#!/usr/bin/env python # coding: utf-8 # # Complex Data Structures: Lists (and Tuples) # # # We have covered in detail much of the basics of Python’s primitive data types: numeric types, strings, and booleans. # We are now going to examine how these basic types can be composed together, in more complex structures. # We will start by examining lists. # # ## Basics of Lists # ### Creating Lists # A list, sometimes called and array, or a vector, is an ordered collection of values. # # In Python, lists are specified by square brackets, `[ ]`, containing zero or more values, separated by commas. # # For example: # In[ ]: number_list = [1, 2, 3, 0, 5, 10, 11] print(number_list) # In[ ]: name_list = ["Panos", "Maria", "Anna", "James"] print(name_list) # In[ ]: mixed_list = ["Panos", 42, "Anna", 22] print(mixed_list) # In[ ]: empty_list = [] print(empty_list) # In[ ]: empty_list = list() print(empty_list) # Lists are a very common data structure, and are often generated as a result of other functions, for instance, the `split(" ")` command for a string, will split a string on space, and then return a list of the smaller substrings. # In[ ]: my_string = "Wow these data structures make for exciting dinner conversation" list_of_words = my_string.split(" ") print(list_of_words) # ### Accessing parts of a list: Indexing and Slicing revisited # # # We can retrieve the value of a particular element of a list by writing in square brackets the location of the element. In python, like most programming languages, list indices start at 0, that is, to get the first element in a list, request the element at index 0. # In[ ]: another_list = ["a", "b", "c", "d", "e", "f"] print(another_list[1]) # Negative indices can be used to traverse the list from the right. # In[ ]: print(another_list[-2]) # If you remember the case with strings and accessing the individual characters, the concept is exactly the same. In fact, strings are treated in Python as lists of characters. # # This also means that the slicing operator works for accessing parts of the list as well: # In[ ]: # Retrieves the elements at positions 2, 3, and 4. # Remember these are the 3rd, 4th, and 5th elements of the list print(another_list[2:5]) # In[ ]: # Retrieved the last 3 elements of the list print(another_list[-3:]) # ### Exercise # You are given a list of names as one big, multiline string. Each line contains one name. # In[ ]: names_string = """Jacob Adams Emily Brown Michael Campbell Madison Davis Andrew Edwards Olivia Flores David Garcia Sophia Ingram Nathan Jones Natalie King""" # * Use the `split` command, appropriately configured, to separate `names` into a list of names. # * Extract the 3rd name from the list # * Extract the second from the last name, using negative indexing # * Retrieve the 6th to 8th names from the list (inclusive, we want a list of 3 names, 6th, 7th, and 8th) # * Retrieve the last 3 names. # In[ ]: # Your code here # #### Solution # In[ ]: # Use the split command, appropriately configured, to separate names into a list of names. # We use the newline character to split the string into one element per *line* names = names_string.split("\n") print(names) # In[ ]: # Extract the 3rd name from the list print(names[2]) # In[ ]: # Extract the second from the last name, using negative indexing print(names[-2]) # In[ ]: # Retrieve the 6th to 8th names from the list (inclusive, we want a list of 3 names, 6th, 7th, and 8th) print(names[5:8]) # In[ ]: # Retrieve the last 3 names print(names[-3:]) # ## Functions that apply to lists # # ### Common Functions # * `len`: The function `len(list)` returns the number of elements in a list. # * `sorted`: The function `sorted(list)` Returns the list sorted # * `max`: Returns the maximum element of a list # * `min`: Returns the minimum element of a list # * `sum`: The function `sum(list)` sums up all the (numeric) elements of a list # # In[ ]: names = ["Panos", "John", "Chris", "Josh", "Mary", "Anna"] # In[ ]: print("Length of names list:", len(names)) # In[ ]: numbers = [3, 41, 12, 9, 74, 15] # In[ ]: print("Length of numbers list:", len(numbers)) # In[ ]: names = ["John", "Panos", "Chris", "Josh", "Mary", "Anna"] print("Sorted List:", sorted(names)) print("Original List:", names) # In[ ]: print("Sorted List:", sorted(numbers)) print("Original List:", numbers) # In[ ]: print("We have ", len(numbers), "numbers") print("Max number:", max(numbers)) print("Min number:", min(numbers)) print("Sum:", sum(numbers)) # In[ ]: # Min and max also operate on strings print("Min name:", min(names)) print("Max name:", max(names)) # ### Exercise # # * Write code that computes the average value of a list of numbers # * Write code that computes the median value of a list of numbers (for simplicity, assume the list contains an odd number of items) # In[ ]: numbers = [3, 41, 12, 9, 74, 15, 5] # your code here # #### Solution # In[ ]: average = sum(numbers) / len(numbers) print("The average is {average:.2f}") # In[ ]: sorted_list = sorted(numbers) # Notice that we use integer division below # as the index value needs to be an integer middle = len(numbers) // 2 median = sorted_list[middle] print("Sorted List:", sorted_list) print("The median is at position ", middle) print("The value of the median is", median) # ## Adding / Removing Elements to a List # # # ### Appending items at the end of a list # + `list.append(x)`: add an element ot the end of a list # + `list_1.extend(list_2)`: add all elements in the second list to the end of the first list. Alternatively, it is possible to use the `+` and concatenate two lists. # In[ ]: # Example of append name_list = ["Panos", "John", "Chris", "Josh", "Mary", "Anna"] name_list.append("Elena") name_list.append("Sofia") print(name_list) # In[ ]: # Example of extend name_list = ["Panos", "John", "Chris", "Josh", "Mary", "Anna"] names_to_add = ["Elena", "Sofia"] name_list.extend(names_to_add) print(name_list) print("Length of list:", len(name_list)) # In[ ]: # List concatenation. This is similar to "extend" name_list = ["Panos", "John", "Chris", "Josh", "Mary", "Anna"] names_to_add = ["Elena", "Sofia"] new_list = name_list + names_to_add print(new_list) # In[ ]: # Notice that append will not work as expected when we pass a list # We now created a "nested" list. We will examine nested lists later name_list = ["Panos", "John", "Chris", "Josh", "Mary", "Anna"] names_to_add = ["Elena", "Sofia"] name_list.append(names_to_add) print(name_list) # Notice that the two lists, created by append and extend, have different lengths print("Length of list:", len(name_list)) # ### Exercise # # * Find out how many names we have in the list of names `names_list` that is the list that we created earlier, from the `names_string` multiline string. # * Add the name `"Mia Lopez"` in the list of names # * Add the list of names `["John Miller", "Jane Nelson"]` in the list of names, using the `extend` command # # In[ ]: names_list = [ "Jacob Adams", "Emily Brown", "Michael Campbell", "Madison Davis", "Andrew Edwards", "Olivia Flores", "David Garcia", "Sophia Ingram", "Nathan Jones", "Natalie King", ] # In[ ]: # your code here # #### Solution # In[ ]: print("The list contains", len(names_list), "names") # In[ ]: new_name = "Mia Lopez" names_list.append(new_name) print(names_list) print("The list contains", len(names_list), "names") # In[ ]: names_to_add = ["John Miller", "Jane Nelson"] names_list.extend(names_to_add) print(names_list) print("The list contains", len(names_list), "names") # ### Inserting and removing items in the list (any position) # # # * `list.insert(index, x)`: insert element x into the list at the specified index. Elements to the right of this index are shifted over # * `list.pop(index)`: remove the element at the specified position # # In[ ]: names_list = [ "Jacob Adams", "Emily Brown", "Michael Campbell", "Madison Davis", "Andrew Edwards", "Olivia Flores", "David Garcia", "Sophia Ingram", "Nathan Jones", "Natalie King", ] # In[ ]: # Insert Jose Hernandez in position 7 names_list.insert(7, "Jose Hernandez") print(names_list) # In[ ]: # It is there print(names_list[7]) # In[ ]: # We can retrieve the element with pop, and then delete it from the list names_list.pop(7) # In[ ]: # Name at position 7 changed print(names_list[7]) # In[ ]: # If we repeat the operation with pop, we will get back the new name that is now in that location names_list.pop(7) # In[ ]: # Name at position 7 changed print(names_list[7]) # ## Finding items in lists # ### Common functions # * `x in list`: checks if `x` appears in the list # * `list.index(x)`: looks through the list to find the specified element, returning it's position if it's found, else throws an error # * `list.count(x)`: counts the number of occurrences of the input element # In[ ]: names = ["Panos", "John", "Chris", "Josh", "Mary", "Anna", "John"] # In[ ]: "Chris" in names # In[ ]: "Peter" in names # In[ ]: # Index name = "Chris" print("Location of", name, "in the list:", names.index(name)) # In[ ]: # Count print("# of John in the list", names.count("John")) print("# of Panos in the list", names.count("Panos")) print("# of Peter in the list", names.count("Peter")) # ### Exercise # # # * Find the name "Josh" in the list. In what position does the name appear? # * Try to find a name that does not exist in the list. What do you get? # # Now let's practice the `if-else` command: # # * Define a variable `search` with the name that you want to search for. # * Check if the name appears in the list # * If yes, then return the index number for its first apperance, and the number of times that you see the name in the list # * If not, print that the name does not appear in the list # In[ ]: names = ["Panos", "John", "Chris", "Josh", "Mary", "Anna", "John"] # In[ ]: # Your code here # #### Solution # In[ ]: search = "John" if search in names: location = names.index(search) count = names.count(search) print("The name", search, "appears", count, "time(s)") print("First appearance at location", location) else: print("The name", search, " does not appear in the list") # ### Exercise # # # Let's apply some of the things that we learned so far in the article below. # In[ ]: washington_post = """Russian officials vehemently defended the country s airstrikes in Syria on Thursday as blows to Islamic State militants even as evidence mounted suggesting that US backed rebels and others were facing the brunt of Moscow s attacks And while Russian officials and diplomats rallied behind President Vladimir Putin the Kremlin s stance appeared further clouded by acknowledgments that the missions have already extended beyond solely the Islamic State In Paris the Russian ambassador to France Alexander Orlov said the Russian attacks also targeted an al Qaeda linked group Jabhat al Nusra or al Nusra Front Syrias ambassador to Russia Riad Haddad echoed that the joint hit list for Russia and the Syrian government included Jabhat al Nusra which is believed to have some coordination with the Islamic State but is still seen mostly as a rival We are confronting armed terrorist groups in Syria regardless of how they identify themselves whether it is Jabhat al Nusra the ISIL or others he said using one of the acronyms for the Islamic State They all are pursuing ISIL ends he added according to the Interfax news agency The ambassadors did not specifically mention any US and Western backed rebel groups But the comment was certain to deepen suspicions by Washington and allies that Putin s short term aim is to give more breathing space to Syria s embattled President Bashar al Assad whose government is strongly supported by Moscow Syrian activists meanwhile ramped up their own claims that Moscow was hitting groups seeking to bring down Assad who has managed to hang on during more than four years of civil war Russia s expanding military intervention in Syria added urgency to separate efforts by Russia and US officials to coordinate strategies against the Islamic State and avoid potential airspace missteps between the two powers so called deconfliction talks The Pentagon said the discussions will begin Thursday One monitoring group the Britain based Syrian Observatory for Human Rights said Russian airstrikes again struck strongholds of an American backed rebel group Tajamu Alezzah in central Hama province The actions quickly criticized by Washington add an unpredictable element to a multilayered war The observatory also reported that airstrikes hit the northwestern city Jisr al Shughour which is in the hands of rebel groups including al Nusra after battles last month to drive back Assad s forces Among the locations hit was a site near Kafr Nabl the northern Syrian town whose weekly protests against the government often featuring pithy slogans in English won it renown as a symbol of what began as a peaceful protest movement against the Assad regime The local council receives US assistance and the rebel unit there has received support under a covert CIA program aimed at bolstering moderate rebels Raed Fares one of the leaders of the protest movement in Kafr Nabl said warplanes struck a Free Syrian Army checkpoint guarding Roman ruins on the outskirts of the town He said the explosion was bigger than anything local residents had seen in three years of airstrikes conducted by Syrian warplanes It made a fire six kilometers wide he told The Washington Post Other sites hit on the second day of Russian bombing included locations in the province of Hama The targets suggested the main intention of the strikes was to shore up government control over a corridor of territory linking the capital Damascus to the Assad family s coastal heartland where the Russians are operating out of an expanded air base Syrian rebels some of them US backed had been making slow but steady gains in the area considered one of the government s biggest vulnerabilities There has been no Islamic State presence there since January when moderate rebels rose up against the extremists and forced them to retreat to eastern Syria In Washington Sen John McCain R Ariz told CNN he could absolutely confirm that airstrikes hit Western backed groups such as the Free Syrian Army and other factions armed and trained by the CIA We have communications with people there said McCain chairman of the Senate Armed Services Committee The accounts could not be independently assessed but the main focus of the Russian attacks appeared to be in areas not known to have strong Islamic State footholds In Moscow the reply was blunt Total rubbish Gennady Zyuganov a member of parliament and leader of Russia s Communist Party said of the US accusations In televised remarks Thursday Putin called accusations that Russian airstrikes had killed civilians in Syria information attacks He also addressed concerns about an accidental military clash between Russian and US led coalition forces saying that his intelligence and military agencies were establishing contacts with counterparts in the United States This work is ongoing and I hope that it will conclude with the creation of a regularly acting mechanism he said A spokesman for Russia s Defense Ministry Igor Konashenkov said Thursday that warplanes hit a dozen Islamic State sites in the past hours destroying targets including a command center and two arms depots The United States and Russia agree on the need to fight the Islamic State but not about what to do with the Syrian president The Syrian civil war which grew out of an uprising against Assad has killed more than people since March and sent millions of refugees fleeing to countries in the Middle East and Europe Accusing Russia of pouring gasoline on the fire Defense Secretary Ashton B Carter vowed that US pilots would continue their year long bombing campaign against the Islamic State in Syria despite Moscow s warning that American planes should stay away from its operations I think what they re doing is going to backfire and is counterproductive Carter said on Wednesday Yet Russia s military flexing in Syria brought quick overtures from neighboring Iraq where the Islamic State also holds significant territory but the government is within Washington s fold Iraq s prime minister Haider al Abadi told France that he would welcome Russia joining the US led airstrikes against Islamic State targets but there have been no specific discussions Joining the protests against the Russian airstrikes was Saudi Arabia a leading foe of Assad and one of Washington s top Middle East allies At the United Nations late Wednesday the Saudi ambassador Abdallah al Mouallimi demanded that the Russian air campaign stop immediately and accused Moscow of carrying out attacks in areas outside the control of the Islamic State In Iran Assad s main regional backer Foreign Ministry spokeswoman Marzieh Afkham called Russia s military role a step toward resolving the current crisis in Syria""" # * What is the length of the document above in characters? In words? In paragraphs (we have one paragraph per line)? # * What is the average length of a paragraph in words? # * What is the average length of a word in characters? (Remember that the document contains spaces and newlines, that should not count as parts of a word) # In[ ]: # Your code here # #### Solution # In[ ]: len_characters = len(washington_post) print("There are", len_characters, "characters") # In[ ]: words = washington_post.split() len_words = len(words) print("There are", len_words, "words") # In[ ]: paragraphs = washington_post.split("\n") len_paragraphs = len(paragraphs) print("There are", len_paragraphs, "paragraphs") # In[ ]: words_per_paragraph = len_words / len_paragraphs print(f"There are {words_per_paragraph:.2f} words per paragraph") # In[ ]: # The document contains spaces and newlines, which should not be counted # If you compute the average word length as len_characters/len_words you will get 6.07, which is incorrect len_letters_only = (len_characters - washington_post.count(" ") - washington_post.count("\n")) letters_per_word = len_letters_only / len_words print(f"There are {letters_per_word:.2f} letters per word") # In[ ]: # Incorrect solution letters_per_word = len_characters / len_words print(f"There are {letters_per_word:.2f} letters per word") # ## Tuples # # # A tuple is similar to a list and consists of a number of values separated by commas. For instance: # In[ ]: t = (12345, 54321, 54321, "hello!") print(t) # The usual slicing and indexing operators still apply: # In[ ]: print(t[3]) # And similarly, we can use the `count` and `index` functions. # In[ ]: t.index("hello!") # However, a tuple but is *immutable*. This means that we cannot modify its contents. So the other operators that modify a list do not apply to a tuple.