Lesson 5. Simple data structures October 9, 2021 1 Introduction • A data structure is no more than a set of data items grouped together into the same variable –> we reserve multiple memory cells and give them a unique name (a string can be considered a very simple data structure) • They allow us to store several data using only one variable • Two types of simple data structures: • To store several data of the same type (arrays): in Python we have lists and tuples • To store some data of different types (records): in Python we have dictionaries and objects 2 Lists • Lists are the equivalent to arrays in other languages • A list is a finite, heterogeneous and mutable collection of elements indexed by position • finite means that there is a maximum number of elements in a list (it depends on the computer) • heterogeneous means that a list may contain data of different types (unlike arrays in other languages that only allow to store data of the same type) • mutable means that the elements of the list can be changed. I can add, delete or modify elements. • indexed by position: we access to each element by its position in the list (similar to the way we access letters in a string) • A string is a finite, homogeneous and inmutable collection of elements (characters) indexed by position • Syntax: list_name = [element1, element2, element3] [1]: # A list of 4 integer elements list1 = [1, 2, 3, 4] # A list of 5 heterogeneous elements list2 = [1, "hello", 3.54, False, 3 + 2j] # I can use [] to access to the elements of a list, the 1st is at position 0 print("The first element of", list1, "is", list1[0]) print("The 2nd element from the end of", list1, "is", list1[-2]) # There is no problem on changing elements inside a list (lists are mutable) list1[2] = "hello" 1 print(list1) print("List is now:", list1) # Max number of elements of a list depends on the computer. To know it: import sys print("Maximum number of elements in a list in this computer:",sys.maxsize) 3 [1, 2, 'hello', 4] Maximum number of elements in a list in this computer: 9223372036854775807 2.1 Ways to create lists • Using literals to enumerate the elements of the list, only usefull in small lists • Using variables to give the values: It copies the value of the variable, there is no relation between the variable and the list element. If one changes the other one does not • Using another list and the + operator: A new list is created with the values of the other list. If one changes the other one does not • Using * to repeat some parts of the list • Using loops: quite used in big lists (an alternative is to use list comprehension) [2]: # Using literals list1 = [1, 2, 3, 4, 5, 6] print("list1 is", list1) # Using variables (the values are copied) a, b, c = 1, 2, 3 # This is read as copy the values of a, b, c into the list list2 = [a, b, c] print("list2 is",list2) # The value of a is changed a = 8 # But the value of the corresponding element of the list is not changed print("list2 is", list2, "it does not change") # Using other list(s) list3 = ["hello", "how", 'are', 'you'] list4 = list1 + list2 + list3 print("list4 is", list4) list1[0] = 0 print("list1 is", list1) print("list4 is", list4, "it does not change") # The + operator needs two lists list1 = list1 + [5] # beware with strings list1 += ["hello"] print(list1) # If we forget the [] when using + the result is different list1 += "hello" print(list1) # Using * 2 list5 = list1 * 2 + [4, 6] print("list5 is", list5) # Using a loop and the + operator list6 = [] for i in range(10): list6 += [i] print("list6 is", list6) list1 is [1, 2, 3, 4, 5, 6] list2 is [1, 2, 3] list2 is [1, 2, 3] it does not change list4 is [1, 2, 3, 4, 5, 6, 1, 2, 3, 'hello', 'how', 'are', 'you'] list1 is [0, 2, 3, 4, 5, 6] list4 is [1, 2, 3, 4, 5, 6, 1, 2, 3, 'hello', 'how', 'are', 'you'] it does not change [0, 2, 3, 4, 5, 6, 5, 'hello'] [0, 2, 3, 4, 5, 6, 5, 'hello', 'h', 'e', 'l', 'l', 'o'] list5 is [0, 2, 3, 4, 5, 6, 5, 'hello', 'h', 'e', 'l', 'l', 'o', 0, 2, 3, 4, 5, 6, 5, 'hello', 'h', 'e', 'l', 'l', 'o', 4, 6] list6 is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2.2 Functions and operators with lists • As we have seen, to access an element we use its index (first is zero). Error if we try to access a non-existing element. If we use negative numbers we start from the end of the list (first is -1). We can also use + and *. • We can use slicing operators [a:b] like in strings • We can use the in and not in operators to check if an element is inside a list or not • Functions that we can use with lists: • print(list): prints all the elements of the list • len(list): returns the number of elements of a list • del(list[index]): removes the element at position index from the list. If you want to delete several elements, it is better to start deleting by the last one (if you delete one, all the remaining ones change their positions). You can use negative numbers to start deleting from the end • del(list): removes all the elements of the list, and also the variable (not really compulsory as python takes care of deleting no longer needed variables) • del(list[pos1:pos2]): removes the elements between pos1 and pos2 (the last not included) [3]: print("list1 is", list1) # Print elements 0 and 1 print("elements 0 and 1 of list1", list1[0:2]) # Unlike in strings (which are inmutable) we can use slicing operators to change # parts of a list # Changing elements 0 and 1 by [9]. You change a sublist by another sublist list1[0:2] = [9] 3 print("list1 is", list1) # To see the length of a list print("list4 is", list4, "and has length", len(list4)) # This removes element 2 del(list4[2]) print("Removed element #2 of list4", list4) # This removes 0 and 1 del(list4[0:2]) print("Now removing 0 and 1", list4) # An alternative way is to do list4[0:2] = [] # This says if 5 is an element of list4 (is element in list?) print("Is there a 5 in list4?", 5 in list4) list1 is [0, 2, 3, 4, 5, 6, 5, 'hello', 'h', 'e', 'l', 'l', 'o'] elements 0 and 1 of list1 [0, 2] list1 is [9, 3, 4, 5, 6, 5, 'hello', 'h', 'e', 'l', 'l', 'o'] list4 is [1, 2, 3, 4, 5, 6, 1, 2, 3, 'hello', 'how', 'are', 'you'] and has length 13 Removed element #2 of list4 [1, 2, 4, 5, 6, 1, 2, 3, 'hello', 'how', 'are', 'you'] Now removing 0 and 1 [4, 5, 6, 1, 2, 3, 'hello', 'how', 'are', 'you'] Is there a 5 in list4? True 2.3 List methods List methods allow us to work with lists. They are like functions that we can apply only to a list. The way to apply them is to put list_name.method() (functions are function(list_name)). We ask the list to execute that method. • append(element): adds an element at the end of the list. It is much more efficient to use list_name.append(element) than list_name = list_name + [element] • insert(index, element): inserts the element at the given position. If the position does not exist it will append it at the end. If you use a negative index, it will start counting from the end: insert(-1, element) is equivalent to append(element) • list1.extend(list2): appends all the elements of list2 to list1. Equivalent to list1 = list1 + list2 but using the + sign is less efficient as a copy of both lists is created and assigned to list1 • index(element): returns the position of the element in the list. Error if the list does not contain the element (I should check first the element is in the list). If the element is repeated it returns the first position • index(element, start, end): returns the position of the element in the sublist between start and end (not included). end is optional. • count(element): returns the number of times the element is in list • clear(): removes all the elements of the list. The list will be the empty one. With the del(list) function you also remove the variable, here the variable exists • remove(element): deletes the first appearance of the element in the list. If the element does not belong to the list, error. With del(list([index]), we remove giving the position, here we give the element 4 • pop(): removes and returns the last element. The element should be stored in another variable. • pop(index): removes and returns the element at a given position. The element should be stored in another variable. • reverse(): reverses the list • sort(): sorts the list. It only works for lists made of similar elements (you cannot sort a list containing both string and numbers) [4]: list1 = [1, 2, 3, 4, 5] # Adding a new element list1.append(0) print("list1 is", list1) # An alternative way is to use + and to include the element into its own list # It is better to use append (it is faster) list1 = list1 + [3] print("list1 is", list1) # beware with strings list1 += ["bye"] print("list1 is", list1) # if we don't enclose the string in a list it considers each element in␣ ,→isolation list1 += "bye" print("list1 is", list1) # Inserts "hello" at the second position of the list list1.insert(2, "hello") print("list1 is", list1) # Append one list to another one with extend # It copies one by one the elements of list2 into list1 list2 = [2.2, "Pepe"] list1.extend(list2) print("list1 is", list1) # Notice that if we append one list to the other, the second is appended # as a single element. Now, the last element of list1 is also a list list1.append(list2) print("list1 is", list1) # Which is the index of 3 in list1? print("First 3 in list 1 is at position", list1.index(3)) # Which is the index of 3 between positions 4 and 8 (in the sublist from 4 to␣ ,→8)? print("There is a 3 at position", list1.index(3, 4, 8), "between elements 4 and␣ ,→8") # If you want to know the types of the elements of a list print("The type of the 6th element of the list is", type(list1[6])) # How many times 3 is in list? print("There are", list1.count(3), "3's in the list") # Removes the first 3 in list3 list1.remove(3) 5 print("list 1 is", list1) # Removing all the elements of list1, list1.clear() print("list 1 is", list1) list1 is [1, 2, 3, 4, 5, 0] list1 is [1, 2, 3, 4, 5, 0, 3] list1 is [1, 2, 3, 4, 5, 0, 3, 'bye'] list1 is [1, 2, 3, 4, 5, 0, 3, 'bye', 'b', 'y', 'e'] list1 is [1, 2, 'hello', 3, 4, 5, 0, 3, 'bye', 'b', 'y', 'e'] list1 is [1, 2, 'hello', 3, 4, 5, 0, 3, 'bye', 'b', 'y', 'e', 2.2, 'Pepe'] list1 is [1, 2, 'hello', 3, 4, 5, 0, 3, 'bye', 'b', 'y', 'e', 2.2, 'Pepe', [2.2, 'Pepe']] First 3 in list 1 is at position 3 There is a 3 at position 7 between elements 4 and 8 The type of the 6th element of the list is <class 'int'> There are 2 3's in the list list 1 is [1, 2, 'hello', 4, 5, 0, 3, 'bye', 'b', 'y', 'e', 2.2, 'Pepe', [2.2, 'Pepe']] list 1 is [] [5]: # Using reverse and sort list1 = [1, 2, 3, 4, 5] list1.reverse() print("list1 is", list1) # Pop removes and returns the last element of the list (with remove the element␣ ,→is # just thrown away) a = list1.pop() print("The variable a, now contains the last element of the list:", a) print("list1 is", list1) # sort the list in order (the elements must be compatible) list2 = [1, 2, 3.1, 0.2, 5, 3] list2.sort() print("list2 is", list2) list1 is [5, 4, 3, 2, 1] The variable a, now contains the last element of the list: 1 list1 is [5, 4, 3, 2] list2 is [0.2, 1, 2, 3, 3.1, 5] 2.4 Comparing lists • We can use == and != to see if two lists are equal or not (they are equal only if they have the same elements in the same order) • We can also use < <= > >= 6 • They compare element by element (first to first, second to second and so on) If both lists are equal then they compare the length [8]: list1 = [1, 2, 3, 4] list2 = [1, 2, 3, 4] list3 = [1, 1, 3, 4, 5, 6, 7] list4 = [1, 2, 3, 4, 5] print("are", list1, "and", list2, "equal?", list1 == list2) # list3 is smaller than list1, because the first different element of list3 is␣ ,→smaller # (the '1' at position number 1) print("is", list3, "less than", list1, "?", list3 < list1) # We can compare lists of heterogeneous elements, given that elements at # similar positions are comparable list5 = [1, "hello", True] list6 = [1, "bye", False] # list 5 is not smaller because the elements 1 start by 'h' and 'b' and 'h'>'b' print("is", list5, "less than", list6, "?", list5 < list6) # list4 > list1 as all the common elemnts are equal but list4 is bigger print("is", list4, "greater than", list1, "?", list4 > list1) # But length is only considered when all the elements are equal # position 1 of list4 is greater than positon1 of list3 print("is", list4, "greater than", list3, "?", list4 > list3) are [1, 2, 3, 4] and [1, 2, 3, 4] equal? True is [1, 1, 3, 4, 5, 6, 7] less than [1, 2, 3, 4] ? True is [1, 'hello', True] less than [1, 'bye', False] ? False is [1, 2, 3, 4, 5] greater than [1, 2, 3, 4] ? True is [1, 2, 3, 4, 5] greater than [1, 1, 3, 4, 5, 6, 7] ? True 2.5 Copying lists • If you assign a list to another list using = they are actually the same list (don’t do it). They behave like this because they are mutable. We can check it using the id() function or the is operator • The is operator tells me if two variables point to the same memory address (identity operator) • Proper ways to copy lists: • • • • Using Using Using Using loops slicing the .copy() method .extends() method on an empty list [9]: list1 = [1, 2, 3, 4] # list1 and list2 are the same list, they point to the same memory address. Is␣ ,→like # having a list with two names list2 = list1 7 list2[0] = "hello" print("list 1 is", list1) # We can use the id() function to check that actually they are the same list print("id of list1", id(list1)) print("id of list2", id(list2)) print("Are list1 and list2 the same list?", list1 is list2) list3 = ["hello", 2, 3, 4] # list1 and list3 are equal print("Are list1 and list3 equal?", list3 == list1) # but they are not the same list (they are at different memory positions) print("Are list1 and list3 the same list?", list3 is list1) # Copying list1 into list2 using loops # We create an empty list and then use a for loop to append elements to it list2 = [] for element in list1: list2.append(element) print("Copying with a loop") print("list2 is", list2) print("Are list1 and list2 the same list?", list2 is list1) print("id of list1", id(list1)) print("id of list2", id(list2)) # Copying list1 into list2 using slicing list2 = list1[:] print("Copying with slicing") print("list2 is", list2) print("Are list1 and list2 the same list?", list2 is list1) print("id of list1", id(list1)) print("id of list2", id(list2)) # Using the copy method (if list2 had any values they are lost) list2 = list1.copy() print("Copying with copy() method") print("list2 is", list2) print("Are list1 and list2 the same list?", list2 is list1) print("id of list1", id(list1)) print("id of list2", id(list2)) # Using extends (the list2 must be empty) list2 = [] list2.extend(list1) print("Copying with extends") print("list2 is", list2) print("Are list1 and list2 the same list?", list2 is list1) print("id of list1", id(list1)) print("id of list2", id(list2)) list 1 is ['hello', 2, 3, 4] id of list1 15282120 8 id of list2 15282120 Are list1 and list2 the same list? Are list1 and list3 equal? True Are list1 and list3 the same list? Copying with a loop list2 is ['hello', 2, 3, 4] Are list1 and list2 the same list? id of list1 15282120 id of list2 80386632 Copying with slicing list2 is ['hello', 2, 3, 4] Are list1 and list2 the same list? id of list1 15282120 id of list2 80414856 Copying with copy() method list2 is ['hello', 2, 3, 4] Are list1 and list2 the same list? id of list1 15282120 id of list2 15278184 Copying with extends list2 is ['hello', 2, 3, 4] Are list1 and list2 the same list? id of list1 15282120 id of list2 80414280 2.6 True False False False False False Lists and loops We use loops to work with lists. The most suitable loop depends on whether you want to change the value of some elements of the list, and if you plan to walk through the entire list or not • If I want to walk through the entire list and I don’t want to change any of its values: for-each loop • If I want to walk through the entire list and I want to change any of its values: for-range loop • If don’t want to walk through the entire list: while [8]: # First example: for each # Adds the elements of the list # We want to walk through the entire list but no changes on it list1 = [1, 2, 3, 4, 5, 6] result = 0 for element in list1: # Each iteration one element of my list is copied into the # element variable, and I work with the copy result = result + element print(result) # Second example: for range # Changes elements with odd value to even value 9 # We want to walk through the entire list and perform some changes for index in range(len(list1)): if list1[index] % 2 != 0: list1[index] += 1 print(list1) # Third example: for range also to create the list # Filling a list with 10 random numbers import random list3 = [] for index in range(10): list3.append(random.randrange(0,100)) print(list3) # Third example: while # We don't know if we need to walk through all the list # Use it both if you want to make changes or not index = 0 found = False while not found and index < len(list1): if list1[index] > 3: found = True else: index = index + 1 if found: print("The element at position", index, "is greater than 3") else: print("No element is greater than 3") 21 [2, 2, 4, 4, 6, 6] [47, 57, 55, 65, 82, 59, 35, 26, 94, 1] The element at position 2 is greater than 3 3 Tuples • A finite, immutable sequence of heterogeneous elements indexed by position • Elements of a tuple can not be changed, neither the length of the tuple • variable = (element1, element2, element3 ...) • Parentheses are optional • Ways to create tuples: • Using literals • Using variables: values of the variables are copied to the tuple. If the variable changes, the tuple does not 10 • • • • Using Using Using Using the + operator and another tuple(s). Values of the tuples are copied the * operator and another tuple a list and casting it to tuple with tuple(list) tuple comprehension (we will not see it) • We can extend a tuple by assigning itself plus the extension, but this is a new tuple (see ids). This is very inefficient in time [13]: # Creating tuples with literals tup = (1, 2, 3, 4, 5) # Parentheses are optional tup2 = "hello", 2, "bye", True, 2.2 # A single element tuple needs a comma at the end tup3 = (1,) # This is not a tuple but a integer 1 is stored not_a_tuple = (1) print("tup is", tup) print("tup2 is",tup2) print("tup3 is",tup3) print("this is not a tuple", not_a_tuple) print("the first element of tup3 is",tup3[0]) # Creating tuples with variables a, b, c = 2, 5, "hello" tup4 = (a, b, c) print("tup4 is", tup4) a = 12 print("The new value of a is", a) print("tup4 is ", tup4, "no changes") # Using the + operator tup5 = tup + tup2 print("tup5 is", tup5) # Using the * operator tup6 = 2 * tup + 3 * tup3 print("tup6 is", tup6) # Using a list and casting it list1 = [1, 2, 3, 4, 5, 6] tup7 = tuple(list1) print("tup7 is", tup7) print("list1 is", list1) list1[0] = 8 print("list1 is", list1, "it changes") print("tup7 is", tup7, "no changes") # We can extend a tuple by assigning itself plus the extension, # but this is a new tuple (see ids). This is very inefficient in time tup8 = (1, 2, 3) print("The id of tup8 is", id(tup8)) tup8 = tup8 + (5, 6, 7) print("Now the id is", id(tup8)) 11 print("tup8 is", tup8) # I cannot change the value of an element, this raises an error # tup8[0] = 7 # This is no longer a tuple, don't do it tup8 = "hello" tup is (1, 2, 3, 4, 5) tup2 is ('hello', 2, 'bye', True, 2.2) tup3 is (1,) this is not a tuple 1 the first element of tup3 is 1 tup4 is (2, 5, 'hello') The new value of a is 12 tup4 is (2, 5, 'hello') no changes tup5 is (1, 2, 3, 4, 5, 'hello', 2, 'bye', True, 2.2) tup6 is (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 1, 1) tup7 is (1, 2, 3, 4, 5, 6) list1 is [1, 2, 3, 4, 5, 6] list1 is [8, 2, 3, 4, 5, 6] it changes tup7 is (1, 2, 3, 4, 5, 6) no changes The id of tup8 is 98162184 Now the id is 97992248 tup8 is (1, 2, 3, 5, 6, 7) 3.1 Why do we use tuples? • We want to have something like a constant containing several values. We mark that the program shouldn’t change it (it is relevant when we are working with functions) • Sometimes Python is forcing us to use tuples (because it needs something immutable) • Using tuples is faster? and requires less memory [10]: tup1 = (1, 2, 3) print("tup1 is", tup1) print("its memory address is", id(tup1)) # This is a totally different variable, I am reusing the name tup1 = (3, 2, 4, 5, 6) print("tup1 is", tup1) print("its memory address is", id(tup1), "it changed, it is another variable") # I am reusing the name (don't do that!!) tup1 = "hello" print("now tup 1 is", tup1, ": it is a string, not a tuple anymore") print("its memory address is", id(tup1), "it changed again") # Sometimes you can see things like this # It may seem a way to extend a tuple, but actually you are creating a new one # Doing it is quite inefficient tup1 = (1, 2, 3) print("tup1 is", tup1) print("its memory address is", id(tup1)) 12 tup1 += (4, 5) print("tup1 is", tup1) print("its memory address is", id(tup1), "it changed, it is another variable") tup1 is (1, 2, 3) its memory address is 91722736 tup1 is (3, 2, 4, 5, 6) its memory address is 91913360 it changed, it is another variable now tup 1 is hello : it is a string, not a tuple anymore its memory address is 94361952 it changed again tup1 is (1, 2, 3) its memory address is 91714064 tup1 is (1, 2, 3, 4, 5) its memory address is 91913360 it changed, it is another variable 3.2 Operators, functions and methods with tuples • • • • • • • • id(tuple): returns the id (memory address) of the tuple type(tuple) del(tuple): erases the tuple and the variable len(tuple): returns the length print(tuple): prints it element in tuple: true if the element belongs to the tuple Slicing to get elements, not to change them (like in strings) .index(x): returns the index of the first element in tuple whose value is x. Error if x does not exist • .index(x, start, end): returns the index of the first element in the subtuple [start, end) whose value is x. Error if x does not exist • .count(x): returns the number of occurrences of x in tuple [14]: print("Is there a 3 in tup?", 3 in tup) print("How many 3's in tup?", tup.count(3)) print("The position of the first 3 in the tuple is", tup.index(3)) Is there a 3 in tup? True How many 3's in tup? 1 The position of the first 3 in the tuple is 2 3.3 Packing and unpacking • Packing means creating a tuple/list using some variables • Unpacking means copying each element of a tuple/list into a variable (as many variables as tuple/list elements are needed) [15]: # This is packing a, b, c, d = 1, 4, 6, 1 list1 = [a, b, c, d] tup1 = (a, b, c, d) 13 # Unpacking (every element of the tuple/list is copied into a variable) # I need to have as many variables as tuple/list elements var1, var2, var3, var4 = tup1 print("var1 is", var1) print("var2 is", var2) print("var3 is", var3) print("var4 is", var4) # The type of each one is different print(type(tup1)) print(type(var3)) var1 is 1 var2 is 4 var3 is 6 var4 is 1 <class 'tuple'> <class 'int'> 4 Casting with lists and tuples • list(collection): converts that collection into a list • tuple(collection): converts that collection into a tuple Usually we work with lists, and once we are sure we don’t want to change them them anymore we cast them to tuples We can also convert a String to a tuple/list or use the range function to create tuples/lists But we cannot use this casting for a number or boolean. Only collections can be converted [13]: # Casting tuple to list and viceversa list1 = list(tup1) tuple1 = tuple([1,2,3]) print(list1) print(tuple1) # Creating a list using range auto_list = list(range(1,11)) print(auto_list) # Not really useful... st = tuple('hello') print(st) [1, 4, 6, 1] (1, 2, 3) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ('h', 'e', 'l', 'l', 'o') 14 5 Nested lists and tuples • A list/tuple inside a list/tuple (matrixes in other languages). A list/tuple whose elements are also lists/tuples [16]: # This is a nested list mat = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(mat) # The first element is also a list (it can be seen as a row in a matrix) print(mat[0]) # Element at column 1 in row 1 print(mat[1][1]) # Changing an element of a sublist mat[1][2] = 33 print(mat) # Appending an element to a sublist mat[1].append(12) print(mat) # Appending an element to the list mat.append(12) print(mat) # Some operations with nested lists # Methods and operators only work with 1st level items, they don't # go inside the sublists print("The length of the list is", len(mat)) print("Is there a 1 in the list?", 1 in mat) print("How many 3's are there?", mat.count(3)) print("Is there a 12 in the list?", 12 in mat) # To count the number of 12's I need to use loops number = 0 # For each element of the list for row in mat: # If the element is a list if type(row) == list: # I use count, as it is a list number = number + row.count(12) else: # If it is not a list, I assume it is a literal # but it may be also a tuple or other not seen yet # data structure like dictionaries or objects. if row == 12: number = number + 1 print("The number of 12's inside the matrix is", number) # I add a sublist inside a sublist mat[2].append([1, 2, 3]) print(mat) # To access one of its elements I need 3 indexes 15 mat[2][3][1] = "hello" print(mat) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] [1, 2, 3] 5 [[1, 2, 3], [4, 5, 33], [7, 8, 9]] [[1, 2, 3], [4, 5, 33, 12], [7, 8, 9]] [[1, 2, 3], [4, 5, 33, 12], [7, 8, 9], 12] The length of the list is 4 Is there a 1 in the list? False How many 3's are there? 0 Is there a 12 in the list? True The number of 12's inside the matrix is 2 [[1, 2, 3], [4, 5, 33, 12], [7, 8, 9, [1, 2, 3]], 12] [[1, 2, 3], [4, 5, 33, 12], [7, 8, 9, [1, 'hello', 3]], 12] 5.1 Copying nested lists • You cannot use = • If you use loops, copy() or slicing you will perform what is called shallow copy. The two lists will be different but their elements will be the same • When working with nested lists you need to use deep copy. Two ways: • Use the copy library: import copy a = copy.deepcopy(b) not to be used this year • Use a nested loop [15]: mat1 = [[1, 2], [3, 4]] mat2 = mat1.copy() print(mat2) print("Are mat1 and mat2 the same matrix?", mat2 is mat1) mat1[0][0] = 12 # If I change one elment of mat1, the same element of mat2 changes # lists are different but elements are pointing to the same address print(mat2, "it has changed!") print("Are the first elements of mat1 and mat2 the same?", mat1[0] is mat2[0]) # We can use the id's to check it print("The id of mat1 is", id(mat1)) print("The id of mat2 is", id(mat2)) print("The id of the first element of mat1 is", id(mat1[0])) print("The id of the first element of mat2 is", id(mat2[0]), "they are equal!") # Copying with copy is like doing mat2[0] = mat1[0] #, mat2[1] = mat1[1] and so on. So mat2[x] and mat1[x] will point # to the same position and will be the same list # The same happens with slicing # And also with a for loop mat2 = [] 16 for element in mat1: mat2.append(element) mat2[0][0] = "changes in both" print("mat1 is", mat1) # Summary: don't use the ways to copy we saw when you have nested lists #(this is called shallow copy in contrast to what is called deep copy) # You can use (not this year) the copy library and the deepcopy() method # or copy with nested loops #Notice that only common elements are the same, if we add a new element in # the other does not change mat1.append(12) print("mat1 is", mat1) print("mat2 is", mat2, "no element added!") # But if we change one of the sublists in any way, the other list changes mat1[0].append(33) print("mat1 is", mat1) print("mat2 is", mat2) mat2[1].append(133) print("mat1 is", mat1) print("mat2 is", mat2) # Nested loop to deep copy # It only works if sublists do not contain lists # We'll see a technique in next lesson to perform deep copy to any level mat2 = [] for item in mat1: # If it is not a list, we append it if not type(item) == list: mat2.append(item) else: # If it is a list, we append an empty list mat2.append([]) # And copy the sublist (-1 is the last element of mat2) mat2[-1] = item.copy() # Now they are not related anymore print("mat1 is", mat1) print("mat2 is", mat2) mat2[0][0] = "no changes" print("mat1 is", mat1) print("mat2 is", mat2) [[1, 2], [3, 4]] Are mat1 and mat2 the same matrix? False [[12, 2], [3, 4]] it has changed! Are the first elements of mat1 and mat2 the same? True The id of mat1 is 94408176 17 one The id of mat2 is 94408056 The id of the first element of mat1 is 94408096 The id of the first element of mat2 is 94408096 they are equal! mat1 is [['changes in both', 2], [3, 4]] mat1 is [['changes in both', 2], [3, 4], 12] mat2 is [['changes in both', 2], [3, 4]] no element added! mat1 is [['changes in both', 2, 33], [3, 4], 12] mat2 is [['changes in both', 2, 33], [3, 4]] mat1 is [['changes in both', 2, 33], [3, 4, 133], 12] mat2 is [['changes in both', 2, 33], [3, 4, 133]] mat1 is [['changes in both', 2, 33], [3, 4, 133], 12] mat2 is [['changes in both', 2, 33], [3, 4, 133], 12] mat1 is [['changes in both', 2, 33], [3, 4, 133], 12] mat2 is [['no changes', 2, 33], [3, 4, 133], 12] 6 Tuples of lists • A tuple that contains a list • As the tuple contains the pointers to the lists and not the lists themselves, we can make changes on the lists, but we cannot remove or add any list to the tuple. [1]: # This is a tuple of lists tup1 = ([1, 2, 3], ["hello", 3, 2.4]) print("the id is", id(tup1)) print("tup1 is", tup1) print("The length of tup1 is", len(tup1)) # Can we change the contents of one the lists? tup1[0].append(4) print("tup1 is", tup1) print("the id is", id(tup1), "it is still the same tuple") # We can even clear one of the lists, but we cannot completely # delete it tup1[0].clear() print("tup1 is", tup1) print("the id is", id(tup1), "it is still the same tuple") the id is 72016200 tup1 is ([1, 2, 3], ['hello', 3, 2.4]) The length of tup1 is 2 tup1 is ([1, 2, 3, 4], ['hello', 3, 2.4]) the id is 72016200 it is still the same tuple tup1 is ([], ['hello', 3, 2.4]) the id is 72016200 it is still the same tuple 7 Dictionaries • Finite, mutable and heterogeneous collection of elements indexed by key (a name) 18 • • • • • • • • Difference with a list: elements are not ordered, no notion of order in a dictionary dic = {key1: value1, key2: value2, key3: value3} A dictionary contains pairs of key and value Similar to records in other languages, they are also known as hash tables, associative arrays, maps, or symbol tables Keys are usually strings but can they can belong to any immutable type (for example tuples, numbers, etc.) Values can be literals, but also lists, dictionaries, tuples… (you can have nested dictionaries) Keys must be unique, if you create two keys with the same name, the second will erase the first To create a new key you only need to give a value to it: dic[new_key] = value [17]: # This is a dictionary, all the keys are str dic1 = {"name": "Pepe", "age": 18, "surname": "Perez"} # But we can have also other types of keys (inmutable) dic2 = {"name": "Pepe", "age": 18, "surname": "Perez", 18: True} # To access an element we use the key # Notice that if the key doesn't exist an error is raised (similar # to trying to access a non-existing index in a list or tuple) print("The value of the 'name' key is", dic1["name"]) # To change the value of an element, we use the key again dic1["surname"] = "Gonzalez" print("dic1 is", dic1) # If we create two equal keys, the second erases the first dic3 = {"name": "Pepe", "age": 18, "surname": "Perez", "age": 20} print("dic3 is", dic3) # Adding a new key dic3["address"]= "Leganés and Colmenarejo" print("dic3 is", dic3, "a new key has been added") # This creates an empty dictionary dic4 = {} print("dic4 is", dic4) The value of the 'name' key is Pepe dic1 is {'name': 'Pepe', 'age': 18, 'surname': 'Gonzalez'} dic3 is {'name': 'Pepe', 'age': 20, 'surname': 'Perez'} dic3 is {'name': 'Pepe', 'age': 20, 'surname': 'Perez', 'address': 'Leganés and Colmenarejo'} a new key has been added dic4 is {} 7.1 • • • • • • Operations, functions and methods with dictionaries To delete a key: del(dic[key]) in operator: to know if a dictionary contains a key print(dict): prints the dictionary len(dict): returns the number of keys del(dict): erases the dictionary and the variable too We cannot copy dictionaries using = (same problem as with lists), we cannot use the + to join 19 • • • • • • • • two dictionaries .copy(): creates a copy of this dictionary (shallow copy) dict1.update(dict2): joins dict1 and dict2 (if any key is repeated it takes the dict2 value) .clear(): erases both keys and values, but keeps the variable .get(key): returns the value of that key, and None if the key is not in the dictionary .values(): returns a list-like with all the values (must be casted to list) .keys(): returns a list-like with all the keys (must be casted to list) .items(): returns list-like with tuples containing keys and values (must be casted to list/tuple) .pop(key): returns the value of that key and removes the key [4]: dic4 = {"name": "Pepe", "age": 18, "surname": "Perez", "key": 20} # Delete a key del(dic4["name"]) print("dic4 is", dic4) print("Does dic4 have the key 'age'?", "age" in dic4) dic5 = dic4.copy() print("dic5 is", dic5, "a copy of dic4") dic6 = {"key1": 11, "key2": True, "key": "common key in both"} dic5.update(dic6) print("dic5 is extended with dic6:", dic5) # Erasing a dictionary dic6.clear() print("dic6 is", dic6) # To get the value of a key # First way: if the key is not existing, you will have an error #print(dic5["name"]) # Recommended way: no error if the key is not existing print("The value of the 'name' key of dic5 is", dic5.get("name")) # To get all the values of the dictionary in a list vals = list(dic5.values()) print("Values of dic5:", vals) # To get all the keys keys = list(dic5.keys()) print("Keys of dic5:", keys) # To get both values and keys val_keys = dic5.items() print("Values and keys of dic5:", tuple(val_keys)) # To get the value of a key and remove it val = dic5.pop('surname') print("dic5 is", dic5) print("the value of the 'surname' key was", val) print("Result of .items() with no casting to list", val_keys) dic4 is {'age': 18, 'surname': 'Perez', 'key': 20} Does dic4 have the key 'age'? True dic5 is {'age': 18, 'surname': 'Perez', 'key': 20} a copy of dic4 20 dic5 is extended with dic6: {'age': 18, 'surname': 'Perez', 'key': 'common key in both', 'key1': 11, 'key2': True} dic6 is {} The value of the 'name' key of dic5 is None Values of dic5: [18, 'Perez', 'common key in both', 11, True] Keys of dic5: ['age', 'surname', 'key', 'key1', 'key2'] Values and keys of dic5: (('age', 18), ('surname', 'Perez'), ('key', 'common key in both'), ('key1', 11), ('key2', True)) dic5 is {'age': 18, 'key': 'common key in both', 'key1': 11, 'key2': True} the value of the 'surname' key was Perez Result of .values() with no casting to list dict_items([('age', 18), ('key', 'common key in both'), ('key1', 11), ('key2', True)]) 21