# Collections

A more detailed treatment of the properties and the manipulation of sequences will be given soon, but let's start off by giving an example of why one might need a data type that holds more than one element in it.

A sequence is an ordered set of objects. We've already seen one kind of sequence, the string, which is an ordered set of characters. Here, we will be looking at ordered sets of numbers

#The basic representation of a sequence of numbers is seq = [5, 3, 2, 1, 7, 20, -10] print seq #to get a handle on elements in this sequence, use the construction seq[i], #remembering that the index starts at 0. print seq[0] #first element in sequence print seq[2] #third element in the sequence #you can also find out how many elements are in a sequence using the #function len() print len(seq) #this way you can print out the last element of the sequence #(always remembering that the index starts at 0) print seq[len(seq)-1] #to get a partial list of elements in the sequence, use the construction #seq[i, k] which will extract the elements between the ith and kth index # not including the element at the kth index -- this is called slicing print seq[2:5] #if no first index is supplied, this will give all elements up to but not #including the second index print seq[:4] #if no second index is supplied, this will give all elements after and #including the first index print seq[4:] #you've also already seen a function that returns a sequence print range(1, 7) #you can also concatenate sequences together using + seq1 = [1.1, 3, 7,1] seq2 = [2,3.5] print seq1+seq2 #or, if you start off with an empty list, you can add one one element at a time seqList = [] seqList.append(4) print seqList seqList.append(101) print seqList #now revisit the divisors example so that instead of printing out all #the divisors, instead store all divisors as a sequence xIn = 270 def myDivisorsStored(xIn): if xIn > 0: seqOut = [] #this initializes the sequence variable for candidate in range(1, xIn): if xIn%candidate ==0: seqOut.append(candidate) return seqOut else: print xIn, " is negative. Please try a positive integer" print myDivisorsStored(270) print myDivisorsStored(2711)

Now for a more complete treatment of lists.

# We've now worked with lists of the same types of objects, like integers listInt = [1, 2, 4, 6, 3, 7, 12] # or lists of points p1 = Point(1, 2, 0) p2 = Point(-2, 3, 0) p3 = Point(-1, 2, 100) listPoints = [p1, p2, p3] #but lists can also comprise heterogeneous objects. listMisc = ["time", 3.15, 10, Point(1, 4, 5)] #notice that even though a list can contain another list, the length of this list is still 4 print len(listMisc) # Lists with consectutive or evenly-spaced integers are so common that there is a built-in # function that does exactly this print range(17) print range(3, 17) print range(0, 21, 3) # and of course there is always the empty list which is often used to assign a list # variable that will be filled in the course of the program listEmpty = []

Lists have the property that they are mutable, meaning that we can change elements within the list.

listInt = [1, 2, 4, 6, 3, 7, 12] # we can change any element in the list listInt[0] = 5 print listInt # or change several elements using slicing listInt[2:4] = [10, 20] print listInt # or change every element in the list; for instance, scaling by a uniform scaling k k = -2 listInt[:] = [k*x for x in listInt] print listInt # we can also delete an element from a list: del listInt[1] print listInt # or several using slicing del listInt[2:5] print listInt

Cloning vs. Aliasing

# Sometimes, it will be useful to modify a list but also to keep a copy of the original. # This is called cloning listIntClone = listInt[:] #Now you can work with listIntClone and this won't affect listInt. listIntClone.append(0) print listIntClone print listInt # Notice that this is different if you were to just assign a list to another. # This is called aliasing listIntAlias = listInt listIntAlias.append(0) print listIntAlias print listInt

Lists and Loops

# The general syntax for lists and while is given by # i = 0 # while i < len(LIST): # VARIABLE = LIST[i] # STATEMENTS # i = i + 1 # # This is equivalent to the syntax for lists and for loops (slightly more concise) # for VARIABLE in LIST: # STATEMENTS # #Iterating through a collection of points can be done concisely as for n, pt in enumerate(pts): print "Point at index ",n," is", pt'

Points along an Interval

# Suppose we want to assign a sequence of points spaced at equal interval size along the x-axis interval_size = 2.5 pts_take1 = [] for i in range(cnt): x = i*interval_size pts_take1.append(Point(x, 0)) # if we now want to draw out a subset of this list of points (pts_x), we can as # easily do this by accessing every other index pts_take1_subset = [] for i in range(0, cnt, 2): pts_take1_subset.append(pts_take1[i]) #this selects every other point # It is often more flexible to specify an interval (a,b) and then divide it # into equal parts. This can be achieved by the following loop a,b = 0,10 pts_take2 = [] for i in range(cnt): x = a + i*(b-a)/cnt pts_take2.append(Point(x, 0)) #We can use the Interval class and list shorthands in Python to reduce this to two lines ival = Interval(a,b) pts_take2 = [Point(t, 0) for t in ival/cnt]

Points along a Grid

""" 2D grids of points cnt_x #number of grid points along the x-direction cnt_y #number of grid points along the y-direction """ #Let's generate a grid of points spaced along equal grid size (in each direction) grid_size = 1.5 pts_take1 = [] for i in range(cnt_x): for j in range(cnt_y): pts_take1.append(Point(i*grid_size, j*grid_size)) #this can be reduced to one line using list shorthands pts_take1 = [Point(i*grid_size, j*grid_size) for i in range(cnt_x) for j in range(cnt_y)] #It is often useful to start with a canvas of fixed dimensions #and then create a grid on top of that with the desired resolution. dim_x = 20 dim_y = 30 pts_take2 = [Point(i*dim_x/cnt_x, j*dim_y/cnt_y) for i in range(cnt_x) for j in range(cnt_y)] #This is already succinct but we can use two intervals and write #this equivalently as follows ival_x = Interval(0, dim_x) ival_y = Interval(0, dim_y) pts_take2 = [Point(u,v) for u in ival_x/cnt_x for v in ival_y/cnt_y] #this makes it flexible to position your canvas (not necessarily at the origin) #eg. try ival_x = Interval(a,b) and ival_y = Interval(c,d)

Lists as Input

# Points along an ellipse can be done using the same syntax as drawing points # along an interval from math import pi, cos, sin ival = Interval(0, 2*pi) pts_ellipse = [Point(10*cos(t), 17*sin(t)) for t in ival.divide(cnt, True)] # Now if we wanted to draw this ellipse at different heights, we can loop across two lists-- # one representing points along any curve drawn in plane and the other holding the # list of heights heights = [0,10,22,32,48,60,75,82,91] pts_tower = [Point(pt.x,pt.y,height) for height in heights for pt in pts_ellipse] # If we want to make this more general so that any floor shape can be used as input, # we can encapsulate this as a function which takes the two lists as input def tower(pts_floor, floor_heights): pts = [Point(pt.x,pt.y,height) for height in floor_heights for pt in pts_floor] return pts #which can be called as follows heights = range(0, 200, 8) drawPointsAtHeights(pts_ellipse,heights) # Inputting a grid of points is also often done. In this case, heights # are proportional to cos(theta) where theta is the angle of incidence def height_field(source, grid): height_lines = [] for pt_grid in grid: v1 = Vec(pt_grid, source).normalized() cos_theta = v1.dot(Vec(0,0,1)) height_lines.append(Line(pt_grid, Vec(0,0,10*cos_theta))) return height_lines pts_out = height_field(pt_source, grid_pts)