Python review. Variables are named locations that can store values. Names of variables: x, y, a, num, howManyTimes. Assignment statements are used to store the value of an expression in a variable. Examples: a = 1 + 2 # 3 is stored in a b = a + 2 # a + 2 becomes 3 + 2 and the resulting value (5) is stored in b b = b + 1 # the value in b is increased by 1 So far we have looked only at expressions formed entirely out of integers. Integers fit in a variable. x = 3 # 3 is stored in x y = x # a copy of x's value is stored in y y = y + 1 # y's value is increased by 1; does this affect x? print x # this prints 3 print y # this prints 4 so y is changed by x isn't; y is a copy of x Strings are different. Examples of strings: "abc", "123", "Hello, World!" Strings are sequences (of characters. Tuples and lists are other examples of sequences.) Strings are bigger, they don't fit in a variable the way numbers do. Instead a pointer to the string's location is stored. a = "nectarine" b = a Now b and a point to the same string, sharing it. Unfortunately there is no way to change a string to see the sharing. But we'll show it with lists, which are mutable (strings are tuples are not). Examples of operations with strings: "blue" + "berry" evaluates to "blueberry" Strings can also respond to various commands: "WHAT IS GOING ON?".lower() evaluates to "what is going on?" "banana".replace('n', 'w') evaluates to 'bawawa' In both cases a new string is generated, the original string remains unchanged. In other words, assume: a = "banana" b = a.replace('n', 'w') Now b is "bawawa" while a remains "banana" as it was to start with. We can get strings from the user with raw_input(...) see below: a = raw_input("Type your name here: ") First the prompt is printed, then the user types and hits enter. The string the user typed then becomes the value of a. If the user types a number we still get a string. To convert it into a number we do this: "23" + "34" evaluates to "2334" int("23") + int("34") evaluates to 57 You can also convert ints to strings with str(...) str(3) + str(5) evaluates to "3" + "5" which evaluates to "35" Since strings are sequences one can index them and get at what they're made of. If a = "abcdefghijk" a[3] evaluates to 'd' a[0] evaluates to 'a' a[-1] evaluates to 'k' a[-2] evaluates to 'j' a[2:3] evaluates to 'c' a[2:5] evaluates to 'cde' a[:4] evaluates to 'abcd' a[4:] evaluates to 'efghijk' a[:] evaluates to 'abcdefghijk' Each time a new string is created. Strings don't know how to calculate/report their length. Instead the len(...) method is used to calculate the length of any sequence. Other methods we've used: abs(...), raw_input(...), int(...), str(...). One other method, range(...), produces a new type of sequence, a list: range(10) evaluates to [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] but more about this later. To define a method you have to have: a) a purpose, which further determines b) a name for the method c) a set of inputs (also called parameters) d) and a return value For example: let's write a method a) to convert a time of day into a number of minutes from the beginning of the day b) with the name of minutes (or any other name you like) c) starting from a string representation like "14:56" (meaning 2:56pm) d) and with the number of minutes as the return value The method is defined as follows: def minutes(time): hours = int(time[0:2]) mins = int(time[3:]) return hours * 60 + mins The special keyword return guarantees that the function returns a value. Here's how one can determine the time (in hours and minutes) from time t1 to time t2. First off, assume t1 < t2 in the usual sense (t1 comes before t2 in the day). Then if t1 and t2 are read from the user and they have the necessary format: a = minutes(t2) - minutes(t1) print a / 60, "hours and", a % 60, "minutes between", t1, "and", t2 Not all problems can be solved by a sequence of assignment statements. For more complex programs we need if statements, while and for loops. The basis for these syntactic constructs is the boolean type: 5 < 3 evaluates to False 2 < 10 evaluates to True The boolean type {True, False} has three operators defined on it: not, and, or. not is unary (like minus) the other are binary (and correspond to * and +). An if statement allows the execution to branch off: preamble [1] if condition: something [2] else: something else [3] regardless [4] We illustrated this with flowcharts: [1] first the preamble (if any) is executed then the condition is tested (it can be True or False) [2] is executed followed by [4] if the condition is True [3] is executed followded by [4] if the condition is False You can embed if statements, we just need to be careful to indent accordingly. The else branch is not mandatory. For structures like this we have a keyword to simplify the code: if condition: something else: if additional condition: something else [A] else: some other something else [B] The code above can be written: if condition: something elif additional condition: something else [A] else: some other something else [B] Loops that we studied and used in our programs have three basic aspects: while some condition is true: do something for variable in sequence: do something for integer variable in range(len(sequence)): do something The first is a more general type of loop. The other two are more specialized, they count a fixed number of times around the loop. In both cases indentation is important, like it is for if statements and method defs. You can have nested ifs, nested loops as well, as we have seen already in assignments. Lists and tuples can have strings as elements like this: a = (1, 2, "three", 4) a is a tuple. a[1] == 2 but "h" == a[2][1] requires two indices as shown. a = [1, 2, "three", 4] In a list elements can change: a[2] = 3 makes a == [1, 2, 3, 4] Tuples like strings can't change. Lists, however, are mutable. Let's take a = [1, 2, 3, 4, 5] and show some more ways to change the list: a[2:4] = [1, 2] makes a == [1, 2, 1, 2, 5] a[:] = [4, 3, 2, 1] changes a completely to a == [4, 3, 2, 1] Recall we said strings are too big to fit in a variable which can hold only their address. All sequences are. But with strings and tuples immutable we couldn't show that. With lists we can devise an experiment to demonstrate that. Lists (like strings) can understand commands. For example: a = [2, 3, 1] a.append(6) adds 6 to the list making a == [2, 3, 1, 6] So the experiment is like this: a = [3, 2, 1] b = a # now the address to a is copied into a a.append(6) # print a now to see it has indeed changed print b # b is still the same as a, the list is shared, a and b are just its names. Other things you can do to a: a[:] = ["nothing", 2, "c", "here"] a[0] = -2 In both cases b sees the changes which proves the sharing. This is important in how a procedure receives a list as an argument: def bubble(nums): while not sorted(nums): for i in range(len(nums)-1): if nums[i] > nums[i+1]: (nums[i], nums[i+1]) = (nums[i+1], nums[i]) This procedure relies on the definition of another method, sorted: def sorted(nums): for i in range(len(nums)-1): if nums[i] > nums[i+1]: return False return True But the idea is that bubble changes the list it receives. It sorts it: >>> a = [3, 2, 4, 1, 5] >>> bubble(a) >>> a [1, 2, 3, 4, 5] >>> We also wrote a method that destroys the list altogether: def select(nums): result = [] while nums: result.append(min(nums)) nums.remove(min(nums)) return result >>> a = [3, 2, 4, 1, 5] >>> b = select(a) >>> a [] >>> b [1, 2, 3, 4, 5] >>> Notice that a list with the desired characteristics is returned. The original list, in the process, is squeezed to nothing. An exercise was stated to write a method that sorts by not affecting the given list: def insertionsort(a): sorted = [] for elem in a: insert(sorted, elem) print "inserting", elem, "gives", sorted # for visualization only return sorted def insert(a, num): if len(a) == 0: a[0:0] = [num] else: for i in range(len(a)): if a[i] > num: a[i:i] = [num] return a[-1:-1] = [num] So we examine the elements of the given list one by one. For each element we insert it where it belongs in a second list originally empty. That list is always sorted, every new number is placed where it should be. The result is that the list always stays sorted. See the printed reports below: >>> a = [3, 2, 4, 1, 5] >>> b = insertionsort(a) inserting 3 gives [3] inserting 2 gives [2, 3] inserting 4 gives [2, 4, 3] inserting 1 gives [1, 2, 4, 3] inserting 5 gives [1, 2, 4, 5, 3] >>> b [1, 2, 4, 5, 3] >>> a [3, 2, 4, 1, 5] >>> Working with methods we asked what it would take for two methods to share a variable. So we wrote this: balance = 0 def deposit(amount): global balance balance = balance + amount def withdraw(amount): global balance balance = balance - amount def report(): print "The current value of balance is:", balance This works as follows: >>> report() The current value of balance is: 0 >>> deposit(3) >>> report() The current value of balance is: 3 >>> withdraw(1) >>> report() The current value of balance is: 2 >>> Notice that you have access to a free variable if you just want to read it (report). To change it you need to declare that variable as being global. Otherwise the variable is entirely local to the method. Finally we discussed dictionaries. Dictionaries are ideal in storing sparse matrices. So we implemented matrices with dictionaries, then tried the same with nested lists. Dictionaries are not sequences. However they're just as easy to use: >>> d = {} >>> d {} >>> d[0, 1] = 3 >>> d {(0, 1): 3} >>> d = {(1, 2) : 4, (4, 0) : -2} >>> d {(1, 2): 4, (4, 0): -2} >>> d[4,0] -2 >>> Obviously dictionaries can have any type of keys they want. Their power is in offering unlimited types of indices, not just ints, as sequences. How do we go through all the entries in a dictionary, though? The answer is that we can get the list of keys, then use it in our going through. So, for example, see: >>> d = {"dgerman" : "something", "lbird" : "dribl", "cbarkley" : "caliendo is terble" } >>> d["lbird"] 'dribl' >>> d['cbarkley'] 'caliendo is terble' >>> d["mjordan"] = "I played my heart out." >>> d {'lbird': 'dribl', 'mjordan': 'I played my heart out.', 'dgerman': 'something', 'cbarkley': 'caliendo is terble'} >>> del d["dgerman"] >>> d {'lbird': 'dribl', 'mjordan': 'I played my heart out.', 'cbarkley': 'caliendo is terble'} >>> d.keys() ['lbird', 'mjordan', 'cbarkley'] >>> for key in d.keys(): print d[key] dribl I played my heart out. caliendo is terble >>> When implementing matrices with dictionaries you need to store the dimensions too. If you implement matrices with nested lists the size is implicit: a = [[1, 2, 3], [4, 5, 6]] This is a matrix with len(a) lines. The lines are len(a[0]) == len(a[1]) long, which is the number of lines. That's what we have done thus far. End of review. In lab we will work out problems, exercises.