<jupyter_start><jupyter_text>Lesson 4: lists, iteration, loopingIn lesson 3, we looked at how to write code that can check for a condition, and carry out different sets of instructions depending on the result of that condition. In this lesson, we look at how to run the same set of code repeatedly on a collection of items.In Python, we have numerous ways to create collections of items. 4 of them are frequently used: `list`s, `dict`s, `set`s, and `tuple`s. We will start with the most basic of them: `list`s.  Python listsIn Python, a list is a simple collection of objects.You can initialise a list from a collection of objects by enclosing them in `[`square brackets`]` and separating them with commas (`,`).Try the following lines of code one by one in the cell below:1. `list_ = [1,2,3,4,5]` (Remember that the semicolon separates two expressions.)3. `list_ = ['apple','blueberry','carrot',]` (A list can contain strings too. And it will ignore any trailing commas.)2. `list_ = [1,'2',3.0,'4.0',5]` (In fact, a list can consist of a mixture of integers, floats, strings, and other objects.)4. `list_ = []` (This is how you initialise an empty list.)Remember that an assignment statement (using the assignment (`=`) operator) will not produce any output. You will have to invoke the variable again on a separate line.<jupyter_code># Write your code here<jupyter_output><empty_output><jupyter_text>List indexingList indexing works very similarly to string slicing ([Lesson 2](lesson_02.ipynbAccessing-subsets-of-strings:-string-slicing)); in fact, strings are implemented in Python internally as a special type of list.Each item in a list is called an **element**. The elements of a list can be retrieved using their **index**.The first element of a list has index `0`. The subsequent elements have an index that is 1 greater than the previous element. List indexes always go in running order.`list[0]` returns the first element of the list. `list[1]` returns the next element, and so on.Try the following lines of code one by one in the cell below:`mylist = [1, 2, 3, 4, 5]`1. `type(mylist)` (A list is a type of object in Python.)2. `mylist[0]` (Try addressing the list with other integers.)3. `mylist[5]` (The largest index in a list of length `n` is `n-1`, because indexes start from 0. Attempting to address a list with an index greater than `n-1` produces an `IndexError`.)4. `mylist[1.0]` (List indexes must be integers.)5. `mylist[]` (You cannot address a list without an index. To refer to the entire list as an object, use the list's variable name, i.e. `mylist`.)6. `mylist[-1]` (Negative integer indexes address the list backward. Try this with `-1`, `-2`,`-3`, etc. What happens when you go below `-4`?)<jupyter_code>mylist = [1, 2, 3, 4, 5] #Do not remove this line.

# Type your code below this line.<jupyter_output><empty_output><jupyter_text>List slicingList slicing allow you to return multiple elements from a list, just as string slicing allows you to return substrings of characters from a string.Try the following lines of code one by one in the cell below:`yourlist = ["Alice", "Bob", "Charlie", "Dawn", "Ernest"]`1. `yourlist[0:0]` (This should give you an empty list.)2. `yourlist[0:1]` (How does this differ from `yourlist[0]`?)3. `yourlist[0:4]` (Why doesn't this return the whole list?)4. `yourlist[0:6]` (If the slice exceeds the largest index, you won't get an error but those indexes will be ignored.)5. `yourlist[0:5:2]` (Can you figure out what the third number in the slice does?)6. `yourlist[0:4:2]` (Why is `yourlist[4]` not in the result?)7. `yourlist[0::2]` (What happens when you remove any of the numbers in the slice? Can you figure out what the default values are?)8. `yourlist[::-1]` (This is a common pattern to get the reverse of a list)9. `yourlist[0:4:-1]` (Why doesn't this work?)10. `yourlist[4:0:-1]` (Is this the same as expression 8?)11. `type(yourlist[0:0])` (A list indexed with a slice always returns a list, even if the result is only an empty list.)<jupyter_code>yourlist = ["Alice", "Bob", "Charlie", "Dawn", "Ernest"] #Do not remove this line.

# Type your code below this line.<jupyter_output><empty_output><jupyter_text>List editing methods A list has built-in methods that allow you to add or remove elements from it.Try the expressions in the cell below, one by one:  (Remember that the assignment (`=`) operator does not produce any output.)`countries = ["America", "Brazil", "Cambodia", "Dominican Republic", "Ethiopia", "France", "Germany", "Hungary"]`  1. `dir(countries)` (This gives you a list of **attributes** that the `samelist` list object has. We will cover attributes in a later lesson, in Object-Oriented Programming)2. `countries.append("India")` (Adds a single item at the end of the list. Calling an object method will not print its value; do you remember how to check an object's value?)3. `countries.append(["India","Japan"])` (Did this produce the effect you expected?)4. `countries.extend(["India","Japan"])` (Adds multiple elements, which must be in a list. How does this differ from `.append()`?)5. `countries.extend("India","Japan")` (`.extend()` method only accepts one value.)6. `countries.extend("India")` (Interesting! If you feed the `.extend()` method with a string instead of a list, what happens? Why?)7. `countries.insert(4,"England")` (The `.insert(n,item)` method lets you insert `item` at the `n`th index.)8. `countries.remove("China")` (You can only remove elements that already exist in the element. Shocking but true. Try this with a country that is already in the list.)9. `countries.remove(0)` (You can't remove the first item in a list this way. Remember that list indexes go in [square brackets].)10. `countries.remove(countries[0])` (Can you explain why this works?)11. `item = countries.pop()` (`.pop()` removes the last element from the list and returns it. You can assign it to a variable, in this case `item`.)12. `item = countries.pop(1)` (`.pop(int)` removes the list element at the (`int`) index stated and returns it. You can assign it to a variable.)13. `countries.clear()` (What does this do?)14. `del countries[0]` (To delete an element from a list, use the `del` keyword on the list element specified by index.)15. `del countries[0:3]` (You can use the `del` keyword to delete multiple elements by list slicing.)<jupyter_code>countries = ["America", "Brazil", "Cambodia", "Dominican Republic", "Ethiopia", "France", "Germany", "Hungary"] #Do not remove this line.

# Type your code below this line.<jupyter_output><empty_output><jupyter_text>Q1: Do these methods work for strings? A1:  BEGIN SOLUTION END SOLUTION  List operatorsThe operators `+` and `*` work with lists. So does the `in` keyword.Try the following expressions in the cell below:(Notice that the list methods above modify the original list. the operators below do not; they give the modified list as a return value.)`anewlist = ["America", "Brazil", "Cambodia", "Dominican Republic", "Ethiopia", "France", "Germany", "Hungary"]`1. `anewlist + "Ireland"` (Doesn't work. The error provides a clue: lists can only be "added" to other lists, not to strings.)2. `anewlist + ["Ireland"]` (This works. You have to convert the string into a list element by putting it in a list first. This is called **list concatenation**. A **new list** is returned containing the result.)3. `anewlist + ["Ireland","Japan","Kenya"]` (You can add lists with multiple elements together this way.)4. `anewlist += ["Ireland"]` (This is equivalent to `anewlist = anewlist + ["Ireland"]`, and is a shorter way to modify the original list.)5. `anewlist[0] = "Australia"` (To reassign a list element to a different value, address the element using its index.)6. `anewlist[0] = Australia` (Remember that strings need the quote marks `''` or `""` otherwise they get interpreted as variables.)7. `anewlist*3` (`*` operator with an integer works on lists too.)8. `anewlist *= 3` (This is equivalent to `anewlist = anewlist*3`)9. `'America' in anewlist` (The `in` keyword checks for exact matches with list elements.)10. `['America'] in anewlist` (Does this work? Why or why not?)11. `'Amer' in anewlist` (Although `'Amer' in 'America'` returns `True`, this statement returns `False`. Why?)<jupyter_code>anewlist = ["America", "Brazil", "Cambodia", "Dominican Republic", "Ethiopia", "France", "Germany", "Hungary"]

# Type your code below this line.<jupyter_output><empty_output><jupyter_text>Useful functions and methods for listsWe often need to know something about the list. Python has built-in functions to give us this information. You have already learnt the `type()` function, which tells us what type of object it is. Try the expressions in the cell below:`numberlist = [8, 7, 6, 5, 4, 3, 3, 2, 1]`1. `len(numberlist)` (`len()` tells you how many elements a list has.)2. `sorted(numberlist)` (`sorted()` gives you a list sorted in ascending order.)3. `sum(numberlist)` (`sum()` returns the sum of all elements. Works on integers and floats only.)4. `min(numberlist)` (`min()` returns the smallest element. Works on strings.)5. `max(numberlist)` (`max()` returns the largest element. Works on strings.)6. `numberlist.index(6)` (`.index(num)` gives you the index of the first occurrence of `num` in the list.)7. `numberlist.count(3)` (`.count(num)` returns the number of occurrences of `num` in the list.)8. `numberlist.reverse()`  -> Examine the value of `numberlist` again; what happened?   (The `.reverse()` method reverses the order of elements in the list without returning any value. This is an alternative to the list-slicing method.)<jupyter_code>numberlist = [8, 7, 6, 5, 4, 3, 2, 1] #Do not remove this line.

# Type your code below this line.<jupyter_output><empty_output><jupyter_text>Q2: Do these functions and methods modify the original list? A2:  BEGIN SOLUTION END SOLUTION Try the expressions in the cell below to understand how these functions work for strings:`countrylist = ["Hungary", "Germany", "France", "Ethiopia", "Dominican Republic", "Cambodia", "Brazil", "America"]`1. `len(countrylist)`2. `sorted(countrylist)`3. `min(countrylist)`4. `max(countrylist)`<jupyter_code>countrylist = ["Hungary", "Germany", "France", "Ethiopia", "Dominican Republic", "Cambodia", "Brazil", "America"] \
#Do not remove this line. Type your code below this line.<jupyter_output><empty_output><jupyter_text>Q3: Are there any differences in the way the functions work for `int`s and `string`s? A3:  BEGIN SOLUTION END SOLUTION  A note on naming variablesWe always write functions with the brackets e.g. `len()`. This is to avoid confusing them with variables. As much as possible, avoid naming your variables in a way that can confuse you.**Negative example:** `len = len(numberlist)` (The first `len` is a variable; the second `len()` is a function)  **Positive example:** `list_len = len(numberlist)` or `list_length = len(numberlist)`  Converting other types to listsPython data types that are collections of objectsTry the following code line and observe the result:1. `list('apple')` (Strings are collections of characters and can be converted to a list.)2. `list(1)` (Integers are not a collection and cannot be converted to a list.)<jupyter_code># Type your code below this line<jupyter_output><empty_output><jupyter_text>Iterating over listsOften, we need to perform more advanced instructions over each item in a collection, and the basic functions will not suffice. In such cases, we need to **iterate** over each item in the collection and carry out a procedure on each item. We can do that using **loops**.  Iterating with a `for` loopRun the following cell and observe the output:<jupyter_code>positions = ['first','second','third','fourth','fifth']

for num in positions:
    print(f'value of positions: {positions}')
    print(f'value of num: {num}')<jupyter_output><empty_output><jupyter_text>Notice how the `num` and `positions` variables are used.`num` is a placeholder. When we start from the first element of `positions` (i.e. `positions[0]`), `num` temporarily holds the value of `positions[0]`. In each iteration, the value of `positions` remains the same, but the value of `num` changes.**Task: Write a list**Complete the code by replacing the underscores (`_____`) with appropriate variable names or strings.In the code cell below, create a list containing the email addresses of your classmates, and print them out using a `for` loop.Hint: Remember that strings need to be initialised within quotes (`''` or `""`)<jupyter_code>emails = [_____,_____,_____,_____,_____]

for _____ in _____:
    # Type your code below
### BEGIN SOLUTION
### END SOLUTION<jupyter_output><empty_output><jupyter_text>Exercise 1: Validate a list of phone numbersIn the code cell below, complete the procedure by replacing the underscores (`_____`) with appropriate expressions to:1. validate each entry in the list `phone_numbers` to check that it is a valid phone number, i.e. obeys the following conditions:   - has 8 digits     - starts with 6, 8, or 9  2. Print out **only the invalid phone numbers**.<jupyter_code>phone_numbers = [68476397,9448756,83674561,48697485] # Do not edit this line.

for _____
    # Type your validation procedure below this line
### BEGIN SOLUTION
### END SOLUTION<jupyter_output><empty_output><jupyter_text>Exercise 2: Filtering lists with `for` loopThe `dir()` function returns all the attributes and methods associated with a Python object, in the form of a list. The object's **special methods** begin and end with a **d**ouble **under**sore (`__`) and are also known as **dunder**s.Complete the code in the code cell below using loops, string methods, and other relevant functions to write a procedure that prints out all the **non-dunder methods** associated with the `string` object.<jupyter_code>str_methods = dir(str)

for _____:
    # Type your code below
### BEGIN SOLUTION
### END SOLUTION<jupyter_output><empty_output><jupyter_text>Generating numbers for iteration: the `range()` functionRun each of the following groups of code and observe the output:1. `range(1,10)` (Hmm, this doesn't seem to do anything ...)2. `list(range(1,10))` (`range()` **generates** a collection of numbers and it can be converted to a list! Notice that the last number is ignored; this is similar to slicing.)3.  ```    for n in range(1,10):        print(n)    ```    (`range()` can be used in a `for` loop to generate numbers for iterating.)4. `list(range(10))` (If only one value is given, this is assumed to be the end value. The start value is assumed to be 0.)5. `list(range(1,10,2))` (Similar to slicing, if 3 values are given, the last value is the step size.)Q4: Which function in Python tells you how to use the `range()` function? Try it on the function and see what it tells you. A4:  BEGIN SOLUTION END SOLUTION  Iterating over a range of numbers with `for` loopSuppose I have two lists:```  positions = ['first','second','third','fourth','fifth']fruits = ['apple','banana','cherry','durian','elderberry']```How would I generate the following output?  ```  The first value is apple.  The second value is banana.  The third value is cherry.  The fourth value is durian.  ...  ```Can I do that with a `for` loop? Absolutely. But it is not possible for us to iterate over two different lists in one loop. Instead, we need to recognise that in the first iteration, we want the first elements from each list, and for the second iteration we need the second elements, and so on.We need to have a way to generate indexes for each iteration. Python makes it easy to do that with the `range()` function.Run the code cell below and observe the output:<jupyter_code>positions = ['first','second','third','fourth','fifth']
fruits = ['apple','banana','cherry','durian','elderberry']

for i in range(len(positions)):
    ith = positions[i]
    name = fruits[i]
    print(f'The {ith} value is {name}.')<jupyter_output><empty_output><jupyter_text>Exercise 3: Predict the outputQ5: What will the output look like with the following code?  ```  for i in range(1,len(positions),2)      ith = positions[i]      name = fruits[i]      print(f'The {ith} value is {name}.')  ``` A5: BEGIN SOLUTION END SOLUTION Q6: What will the output look like with the following code? Why?  ```  for i in [0,1,2,3,4,5]:      ith = positions[i]      name = fruits[i]      print(f'The {ith} value is {name}.')  ``` A6:  BEGIN SOLUTION END SOLUTION Q7: What error will you get with the following code? Why?  ```  for i in [0,1,2,3,4,5]:      ith = positions      name = fruits      print(f'The {ith} value is {name}.')  ``` A7:  BEGIN SOLUTION END SOLUTION Q8: What will the output look like with the following code? Why?  ```  for i in range(len(fruits)):      ith = positions(i)      name = fruits(i)      print(f'The {i} value is {name}.')  ``` A8:  BEGIN SOLUTION END SOLUTION Q9: What will the output look like with the following code? Why?  ```  for i in range(0,len(positions)):      print(f'The {positions[i]} value is {fruits[i]}.')  ``` A9:  BEGIN SOLUTION END SOLUTION  Exercise 4: Make a menuWrite code in the code cell below to create a menu and ask the user for input.Sample output:```    == Menu options ==    1. Show the time    2. Round a number to the nearest sf    3. Round a number to the nearest dp    4. Convert temperatures        Choose an option (1-4): ```Your code should store the menu options in a list and generate the options in a `for` loop, so as to allow future developers to extend it easily.<jupyter_code>"""
When creating long collections, Python allows you to break up the line of code 
for easier reading, so long as the line break happens after a comma (,) and
before the end of the collection object.

If you need to write multiline comments, the best way to do so is using three 
quote marks at the start and end, on a new line, like this comment.
"""

menu_options = ['Show the time',
                'Round a number to the nearest sf',
                'Round a number to the nearest dp',
                'Convert temperatures']

# Type your code below:
### BEGIN SOLUTION
print('== Menu options ==')
for n in range(4):
    print(str(n+1)+'.',menu_options[n])
### END SOLUTION<jupyter_output><empty_output><jupyter_text>Errors in Python: `IndexError`When you try to slice a list or obtain an element from a list using an invalid index, Python will halt and raise an `IndexError`. This often happens if you accidentally use a `string` instead of an `int`, or if you use the wrong variable. Another way to get `IndexError` is when your index is equal to or larger than the length of the list.In the code cell below, try to raise an `IndexError`.<jupyter_code>fruits = ['apple','banana','cherry','durian','elderberry']

# Type your below this line to raise an IndexError<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11287834459279748, 'fraction_numerical': 0.014947603595935954, 'mean_word_length': 4.793528505392912, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '242553', 'license': 'MIT', 'n_tokens_mistral': 5728, 'n_tokens_neox': 5385, 'n_words': 2598, 'path': 'Lessons-Basic/lesson_04.ipynb', 'repo_name': 'nyjc-computing/intro-to-python', 'size': '18799'}