# Tuples, Lists, Aliasing, Mutability, and Cloning ## Tuples Ordered sequence of elements, **can mix element types**, *immutable* ``` >>> t = (2,"something",3) >>> t[0] 2 >>> tuples = t + ("here",21) >>> print(tuples) (2, 'something', 3, 'here', 21) >>> tuples[1:5] ('something', 3, 'here', 21) >>> len(tuples) 5 >>> tuples[1]=5 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment ``` Tuples are similar to strings, they contain elements where `t[start,stop,step]` is used to slice tuples. They are also immutable, u can't change an element directly in a tuple. U can also mix existing tuples with other elements. ## Applying Tuples - used to conveniently swap variable values ```python #this is not allowed x = y y = x #this is allowed temp = y y = x x = temp #this is also allowed (x,y) = (y,x) ``` Applying this in a function, u can **return more than one value:** ```python def quotient_and_remainder(x,y): q = x//y r = x%y return(q,r) (quot,rem) = quotient_and_remainder(4,5) ``` `(quot,rem) = quotient_and_remainder(4,5)` calls the function, mapping 4 and 5 into the function. Function runs through the operations and returns the values back. `x//y` is the product of a division rounded to the nearest whole number. ## Manipulating Tuples Iterating over tuples: ```python def get_data(aTuple): nums = () # empty tuple words = () for t in aTuple: # concatenating with a singleton tuple nums = nums + (t[0],) # only add words haven't added before if t[1] not in words: words = words + (t[1],) min_n = min(nums) max_n = max(nums) unique_words = len(words) return (min_n, max_n, unique_words) ``` **Multiple data can be collected simultaneously** using tuples. With this example we initially set nums and words as an empty tuple. Inside the for loop **both tuples are replaced with new tuples that corresponds to a certain set of data.** In this case nums always collects the `(t[0],)` position of the input, which is the int; words will always collect `(t[1],)` position, which is the string. Do note function `min()` and `max()` select the minimum and maximum value given from the input. `unique_words` is a variable to collect the total number of **unique** words (defined as `if t[1] not in words:`) Thus, the function returns three variables. ```python def get_data(aTuple): """ aTuple, tuple of tuples (int, string) Extracts all integers from aTuple and sets them as elements in a new tuple. Extracts all unique strings from from aTuple and sets them as elements in a new tuple. Returns a tuple of the minimum integer, the maximum integer, and the number of unique strings """ nums = () # empty tuple words = () for t in aTuple: # concatenating with a singleton tuple nums = nums + (t[0],) # only add words haven't added before if t[1] not in words: words = words + (t[1],) min_n = min(nums) max_n = max(nums) unique_words = len(words) return (min_n, max_n, unique_words) tswift = ((2014,"Katy"), (2014, "Harry"), (2012,"Jake"), (2010,"Taylor"), (2008,"Joe")) (min_year, max_year, num_people) = get_data(tswift) print("From", min_year, "to", max_year, \ "Taylor Swift wrote songs about", num_people, "people!") ``` This is an example given from the lecture where data can be sorted to count how many people Taylor Swift has written songs about. [Visualization](https://pythontutor.com/render.html#code=def%20get_data%28aTuple%29%3A%0A%20%20%20%20nums%20%3D%20%28%29%20%20%20%20%23%20empty%20tuple%0A%20%20%20%20words%20%3D%20%28%29%0A%20%20%20%20for%20t%20in%20aTuple%3A%0A%20%20%20%20%20%20%20%20%23%20concatenating%20with%20a%20singleton%20tuple%0A%20%20%20%20%20%20%20%20nums%20%3D%20nums%20%2B%20%28t%5B0%5D,%29%20%20%20%0A%20%20%20%20%20%20%20%20%23%20only%20add%20words%20haven't%20added%20before%0A%20%20%20%20%20%20%20%20if%20t%5B1%5D%20not%20in%20words%3A%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20words%20%3D%20words%20%2B%20%28t%5B1%5D,%29%0A%20%20%20%20min_n%20%3D%20min%28nums%29%0A%20%20%20%20max_n%20%3D%20max%28nums%29%0A%20%20%20%20unique_words%20%3D%20len%28words%29%0A%20%20%20%20return%20%28min_n,%20max_n,%20unique_words%29%0A%0Atswift%20%3D%20%28%282014,%22Katy%22%29,%0A%20%20%20%20%20%20%20%20%20%20%282014,%20%22Harry%22%29,%0A%20%20%20%20%20%20%20%20%20%20%282012,%22Jake%22%29,%20%0A%20%20%20%20%20%20%20%20%20%20%282010,%22Taylor%22%29,%20%0A%20%20%20%20%20%20%20%20%20%20%282008,%22Joe%22%29%29%20%20%20%20%0A%28min_year,%20max_year,%20num_people%29%20%3D%20get_data%28tswift%29%0Aprint%28%22From%22,%20min_year,%20%22to%22,%20max_year,%20%5C%0A%20%20%20%20%20%20%20%20%22Taylor%20Swift%20wrote%20songs%20about%22,%20num_people,%20%22people!%22%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) ## Lists Lists contains elements to create an ordered sequence, accessible by index. similar to tuples, a list is denoted by [ ]. ### **Lists are mutable objects** U can change elements in a list **directly**, this is not allowed in strings and tuples. ``` >>> L = [2,'a',4,[1,2,3]] >>> len(L) 4 >>> L[0] = 1 >>> print(L) [1, 'a', 4, [1, 2, 3]] ``` Similar operations can be done: ``` >>> len(L) 4 >>> L[2] 4 >>> L[3] [1, 2, 3] ``` A variable can be given values to subtract from other values in `L[]` to print an element: ``` [1, 'a', 4, [1, 2, 3]] >>> i = 3 >>> L[i-1] 4 ``` ## Iterating Over a List This is a common pattern to compute the **sums of elements:** ```python total = 0 L = [1,2,3,4,5] for i in range(len(L)): total += L[i] print(total) ``` `total` will be printed 15 in the terminal. We can modify this for a slightly more readable code: ```python total = 0 L = [1,2,3,4,5] for i in L: total += i print(total) ``` ## Operations On Lists: Add/Remove Since lists are mutable, we can add and remove elements from it. ### Adding Elements Here we use `append` to add elements on the end of the list: ``` >>> L = [1,2,3] >>> L.append(5) >>> print(L) [1, 2, 3, 5] >>> ``` Operator `+` can be used to make a new list combining multiple existing lists: ``` >>> L1 = [1,2,3] >>> L2 = [4,5,6] >>> L3 = L1+L2 >>> print(L3) [1, 2, 3, 4, 5, 6] ``` `extend` can be used to mutate a list: ``` >>> L = [1,2,3] >>> L.extend([4,5,6]) >>> print(L) [1, 2, 3, 4, 5, 6] ``` ### Removing Elements U can use `L.remove()` to remove a specific element, python looks for the element from the parentheses and deletes it from the list, two rules to keep note: - if element occurs multiple times, removes first occurrence - if element is not on list, error feedback is given ``` >>> L = [1,2,3,4,5,6] >>> L.remove(2) >>> print(L) [1, 3, 4, 5, 6] ``` ``` >>> L = [1,2,3,3,4,5] >>> L.remove(3) >>> print(L) [1, 2, 3, 4, 5] ``` ``` >>> L = [1,2,3,4,5] >>> L.remove(6) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list ``` U can also delete a specific index using `del(L[index])`: ``` >>> L = [1,2,3,4,5] >>> del(L[2]) >>> print(L) [1, 2, 4, 5] ``` `L.pop()` deletes the element on the end of the list and returns it: ``` >>> L = [1,2,3,4,5] >>> L.pop() 5 >>> print(L) [1, 2, 3, 4] ``` *Note: Use it cautiously in functions, it might return* `None` ## Converting Lists - Strings ### Strings to Lists After setting a string variable, u can convert it to a list using `list()` *including spaces*: ``` >>> s = "python cool" >>> list(s) ['p', 'y', 't', 'h', 'o', 'n', ' ', 'c', 'o', 'o', 'l'] ``` Python returns the list with it's individual elements back after converting it. **However, s will remain as a string. Printing s will still display it's string form:** ``` >>> s = "python cool" >>> list(s) ['p', 'y', 't', 'h', 'o', 'n', ' ', 'c', 'o', 'o', 'l'] >>> print(s) python cool >>> print(list(s)) ['p', 'y', 't', 'h', 'o', 'n', ' ', 'c', 'o', 'o', 'l'] ``` `s.split` can be used to split a string on a character, splits on spaces if called without a parameter: ``` >>> s.split("p") ['', 'ython cool'] ``` *note that characters are not individually separated, comparing to* `list()` ``` >>> s = "phyton cool" >>> s.split() ['phyton', 'cool'] ``` ### Lists to Strings Use `''.join()` to turn a list of characters into a string: ``` >>> L = ["1","2","3"] >>> ''.join(L) '123' ``` adding characters in between quotes will result in the characters be placed between every element: ``` >>> L = ["1","2","3"] >>> "add".join(L) '1add2add3' ``` ## Other List Operators ### `sort()` vs `sorted()` vs `reverse()` `sort()` and `sorted()` can be used to sort elements in an ascending order: ``` >>> L = [4,2,3,1] >>> sorted(L) [1, 2, 3, 4] >>> print(L) [4, 2, 3, 1] ``` `sorted()` **returns the sorted list but doesn't mutate it** ``` >>> L = [4,2,3,1] >>> L.sort() >>> print(L) [1, 2, 3, 4] ``` `sort()` **doesn't return the sorted list but does mutate it** `reverse()` mutates the lists as well: ``` >>> L = [1,4,2,3] >>> L.reverse() >>> print(L) [3, 2, 4, 1] ``` *note:* `reverse()` *only reverse the positions of elements,* ***it doesn't sort the elements in a descending order mathematically*** ### Other operations can be seen here: https://docs.python.org/3/tutorial/datastructures.html ## Lists in Memory and Aliases *Minor warning for using lists* - lists are mutable - behave differently than immutable types - is an object in memory - variable name points to object Any variable pointing to that object is **affected**, so working with lists might have **side effects** Example below: ``` >>> warm = ['red','yellow','orange'] >>> hot = warm >>> hot.append('pink') >>> print(hot) ['red', 'yellow', 'orange', 'pink'] >>> print(warm) ['red', 'yellow', 'orange', 'pink'] ``` **Assigning a variable to another variable, changing either one will affect the other.** This is the distinct difference between strings and lists. List is special for it's mutability. ## Cloning Lists We can use this speciality to clone lists. `[:]` is used to copy a list from a variable to another: ``` >>> coding_language = ['python','java','html'] >>> programming_language = coding_language[:] >>> programming_language.append('C') >>> print(programming_language) ['python', 'java', 'html', 'C'] >>> print(coding_language) ['python', 'java', 'html'] ``` `[:]` can be seen as `[0:len()]`. This method is desirable to prevent the side effect. Cloning a list allows python to **create a copy from the existing list onto a new list**, so changing a variable won't affect the other. ## Sorting Lists example is given at `sort()` vs `sorted()` vs `reverse()`. Here we will be specifically talking about `sort()` vs `sorted()`: ``` >>> warm = ['orange','yellow','red'] >>> sortedwarm = warm.sort() >>> print(warm) ['orange', 'red', 'yellow'] >>> print(sortedwarm) None ``` `sort()` returned nothing because it already mutated the list, so `sortedwarm` prints out `None`. ``` >>> cool = ['blue','aqua','green'] >>> sortedcool = sorted(cool) >>> print(cool) ['blue', 'aqua', 'green'] >>> print(sortedcool) ['aqua', 'blue', 'green'] ``` `sorted()` returns the sorted list but doesn't mutate it, so `sortedcool` can be printed out. ## Lists of Lists of Lists of... Nested lists are allowed but side effects may be prevalent after mutation. ``` >>> warm = ['yellow','orange'] >>> hot = ['red'] >>> brightcolors = [warm] >>> brightcolors.append(hot) >>> print(brightcolors) [['yellow', 'orange'], ['red']] >>> hot.append('pink') >>> print(hot) ['red', 'pink'] >>> print(brightcolors) [['yellow', 'orange'], ['red', 'pink']] ``` This test begins with two variables `warm` and `hot`. We then assign `brightcolors` variable to contain elements in `warm`. So `brightcolors` has `[['yellow','orange']]`. Adding `hot` into `brightcolors` makes `brightcolors` a variable containing elements from `warm` and `hot`. What if we add elements to existing variables like `hot`? Answer: `brightcolors` will be affected as well. ### Be cautious when adding or removing elements from variables, especially when handling lists in for loops. Use the clone operator when necessary to avoid side effects.