6. Lists#

In this notebook, we cover the following subjects:

  • Iterable Objects;

  • List Indexing and Slicing;

  • Mutability;

  • Operations;

  • List Methods;

  • List Functions;

  • List and Strings.


# To enable type hints for lists, we need to import the following:
from typing import List

In this notebook, we will deepen our knowledge about lists. It is possible that you will see some of the concepts that were explained in notebook 5 (for loop), but it is always good to revisit these ideas for reinforcement.

6.1. Iterable Objects#

When you can loop over (i.e., iterate through) an object, accessing each element of this object, we speak of an iterable. You can think of an iterable object as a container that holds multiple items, and you have the ability to inspect each item in a systematic way.

So far, we have explored various data types, and it’s important to note that while some of these data types are iterable, others are not:

Data Type

Iterable

Lists

Yes

Strings

Yes

Tuples

Yes

Sets

Yes

Dictionaries

Yes

Integers

No

Floats

No

Booleans

No

None (NoneType)

No

Custom Objects

It depends (can be made iterable)

If some of these data types are new to you, don’t worry; these will be explained in other notebooks.

6.1.1. An Example: Strings#

As the table suggests, strings are iterable. However, you might be wondering how. If you look closely at a string, it is simply a collection of characters. To come back to our previous analogy, the string can be viewed as a container, with each character being an item within that container. Let’s look at an example.

favourite_movie_character = "Rubeus Hagrid"

Here, we created a new variable and assigned the string “Rubeus Hagrid” to it. How can we access the individual elements?

  1. Indexing

Assume you just want to know the first letter of the character’s name. Python allows you to do this through indexing, where you can select one of the characters in a string. Indexing can be done on many, but not all, iterables, including sequences like strings, lists, and tuples, as well as dictionaries using keys. It works as follows:

print(favourite_movie_character[0])
R

We can also assign the result to a new variable:

first_letter_of_favourite_movie_character = favourite_movie_character[0]

print(first_letter_of_favourite_movie_character)
R

The expression in square brackets is called an index and it only takes integer values. The index indicates which element in the sequence you want (hence the name). The first element in an iterable is obtained by index 0.

0

1

2

3

4

D

o

b

b

y

info = "Dobby"[0]
print(info)
D

Note

Strings are immutable, meaning it is not possible to change an existing string. You must create a new string if you want to change an existing one.

In Python, you are also allowed to do negative indexing. What will the following cell print?

info = "Dobby"[-1]
print(info)
y

You can access elements from the back using negative indexing, where -1 corresponds to the last element.

-5

-4

-3

-2

-1

D

o

b

b

y

  1. Slicing

You might like to obtain a segment of a sequence, rather than accessing just a single element. This is where slicing comes in, allowing you to extract segments (i.e, slices) from many iterables, such as strings, lists, and tuples. The syntax looks as follows:

slice = sequence[start:stop:step]
  • sequence: A string, list, tuple, or any other iterable that supports indexing.

  • start: The inclusive index where the slice begins.

  • stop: The exclusive index where the slice ends.

  • step: The step size between elements in the slice, default is 1.

Let’s demonstrate slicing with strings. First, we assign the string “Artificial Intelligence” to a variable.

ai_str: str = "Artificial Intelligence"

We can obtain the segment “Intelligence” as follows:

intelligence_str: str = ai_str[11:]

print(intelligence_str)
Intelligence

Note

The above code snippet works by slicing the string stored in ai_str. Notice that we use [11:], meaning that we start from the value at the 11th index and go untill the end of the string.

Now, let’s think. What will be the output of the following code cells?

slice_ai_str: str = ai_str[:10]

print(slice_ai_str)
Artificial
slice_ai_str: str = ai_str[:10:-1]

print(slice_ai_str)
ecnegilletnI
slice_ai_str: str = ai_str[::-2]

print(slice_ai_str)
englen acftA
slice_ai_str: str = ai_str[:11:2]

print(slice_ai_str)
Atfca 

6.2. List Indexing and Slicing#

Lists are a data type that hold a collection of elements or items, where the elements can be of any data type (even other lists). Lists can also contain duplicate elements.

Lists can be recognized by their square brackets [], and the syntax is as follows:

list_name: list = [element1, element2, element3, ...]

The items in a list are ordered as each item corresponds to a position (i.e., index). Therefore, similar to strings, indexing and slicing can be applied to lists. Let’s look as some examples.

characters: List[str] = ["Harry", "Ron", "Hermione", "Malfoy", "Voldemort"]
# What will the following print statements result in?
print(characters[0])
print(characters[-1])
print(characters[1:3])
print(characters[1::2])
Harry
Voldemort
['Ron', 'Hermione']
['Ron', 'Malfoy']

Note

Remember, the typehint for a list is:
variable: List[data type of values in the list]

It is possible to store values in a list which has different data types. In that case, just use the any keyword.

6.3. Mutability#

Unlike strings, lists are mutable, meaning you can modify an existing list. The syntax we use for this is very similar to indexing and slicing.

First, we define an initial list.

pets: List[str] = ["Dog", "Cat", "Fish", "Lion", "Bird", "Rabbit", "Hamster"]

Oops! We made a mistake because lions aren’t really pets, so we want to replace it with a turtle.

We first need to find the index of the string “Lion”. Although the list is short, there’s a simple list method that can help us, which is especially useful when working with longer lists: .index(). We’ll discuss more list methods later in the notebook.

print(pets.index("Lion"))
3

The output shows that the string "Lion" is located at index 3. Now, we want to replace it with the string "Turtle".

pets[3] = "Turtle"

What will happen if we print the list again?

print(pets)
['Dog', 'Cat', 'Fish', 'Turtle', 'Bird', 'Rabbit', 'Hamster']

Indeed, the string "Lion" is replaced with "Turtle". We can even mutate a segment.

pets[:2] = ["Snake", "Pig"]

What will the output be?

print(pets)
['Snake', 'Pig', 'Fish', 'Turtle', 'Bird', 'Rabbit', 'Hamster']

6.3.1. The in Operator#

In addition to modifying the list, we can also use the in operator to check if certain elements are in the list. The result is a boolean value, which is handy, especially when dealing with longer lists.

print("Snake" in pets)
True

Note

The in operator can also be used on strings.

These boolean expressions can be used to determine the flow of your program.

if "Lion" in pets:
    print("A lion as a pet? Are you sure?")
else:
    print("Luckily, lions aren't allowed as pets—they're far too dangerous!")
Luckily, lions aren't allowed as pets—they're far too dangerous!

Now, with everything we’ve learned so far, we can create more advanced boolean expressions. What will be the output of this cell?

boolean_exp: bool = (("VU Amsterdam"[:2] == "VU") or (10 ** 2 == 1000)) and ("v" in "obvious") and (not (4 > 9 or 8 < 2))
print(boolean_exp)
True

6.4. Operations#

Remember, operators can also be used on non-numeric data types, such as lists.

# Applying the + operator

a : List[int] = [1, 2, 3]
b : List[int] = [4, 5, 6]
c : List[int] = a + b

print(c)
[1, 2, 3, 4, 5, 6]
# And the * operator

a : List[int] = [1, 2, 3] * 5

print(a)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

6.5. List Methods#

In Python, there are many methods that operate on lists. However, it might take some time to learn these methods by heart but luckily the dir() function makes this process a lot easier.

6.5.1. The dir() Function#

The dir() function returns all the attributes and methods that you can use on an object. For example:

a_list: List[int] = [1, 2, 3]

print(dir(a_list))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

You can also pass the data type itself as an argument to the dir function.

print(dir(list))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

6.5.2. The help() Function#

It might still be a bit unclear what you can do with the output of the dir() function. Thankfully, the methods in Python are often clearly named, suggesting their functionality. To understand the usage of the methods, the help() funtion can be used. Let’s use the .append() method as an example.

help(list.append)
Help on method_descriptor:

append(self, object, /)
    Append object to the end of the list.

The output of the help() function is extremely helpful. First of all, it explains the usage of the method in plain English, namely to append an object to the end of a list. Secondly, it shows you all the parameters a method has. We’ll deconstruct its components.

  • self: The self parameter refers to an instance of the object and is, when you call the method, automatically passed by Python. Don’t worry too much about self for now; it will be explained in more detail in the notebook on classes and methods.

  • object: The object parameter is something you provide when you call the method, as it represents the element you want to add to your list.

  • /: The parameters before the / symbol are what we call positional-only parameters. This means you have to pass the arguments in the specific order defined by the function’s definition. The / symbol itself is not a parameter.

Note

In Python, the dir() and help() functions can be applied to any object, module, class, function, or data type. Together, these functions provide a clear overview of their documentation, attributes, and methods.

6.5.3. Some Methods#

Let’s look at some list methods.

  1. The .append Method

As mentioned above, we use the .append() method when we want to insert an element at the end of a list. We first initialise the list sports.

sports: List[str] = ["football", "hockey", "tennis"]

print(sports)
['football', 'hockey', 'tennis']

Since this list is quite short, we should add a sport.

sports.append("baseball")

When we print the list again, we see that the new item is added to the list.

print(sports)
['football', 'hockey', 'tennis', 'baseball']
  1. The .insert() Method

But what if you want to insert it somewhere else in the list? Well, then you can use .insert().

sports.insert(2, "swimming")

print(sports)
['football', 'hockey', 'swimming', 'tennis', 'baseball']
# What will happen when we use insert() outside the index bounds?

sports.insert(12, "skating")

print(sports)
['football', 'hockey', 'swimming', 'tennis', 'baseball', 'skating']
  1. Deleting: .remove() and pop

There are several ways to remove elements from a list, but the most common are .remove() and .pop().

The .remove() method:

  • Removes the first occurrence of a specific value from the list.

  • It does not return the removed element, only removes it.

  • If the value does not exist in the list, it raises a ValueError.

Use case: When you want to remove an item by its value rather than by its index.

sports.remove("tennis")

print(sports)
['football', 'hockey', 'swimming', 'baseball', 'skating']
# What happens if you have multiple elements of the same value in a list and you use remove()?

a_list: List[int] = [1, 1, 2, 3, 1, 8, 7]
print(a_list)

a_list.remove(1)
print(a_list)
[1, 1, 2, 3, 1, 8, 7]
[1, 2, 3, 1, 8, 7]

As mentioned above, the .remove() method will only remove the first occurence of a specific value from a list. If you would like to remove all occurences, consider using a while loop combined with the .remove() method. Alternatively, you can check out the filter() method.

The .pop() method:

  • Removes and returns an element from the list based on its index. (default is the last element if no index is provided)

  • If the index is out of range, it raises an IndexError.

Use case: When you want to remove an item by its index and you might want to use the removed value.

popped_element = sports.pop(0)

print(sports)
print(popped_element)
['hockey', 'swimming', 'baseball', 'skating']
football
# What happens if you use pop with no parameter?

sports.pop()
print(sports)
['hockey', 'swimming', 'baseball']

Remember, if no index is provided, the defualts is to pop the last element.

  1. Copying: .copy() and =

There are two main ways to copy the contents of one list into another:

  • Using the .copy() method.

  • Using the = operator.

However, these two methods behave differently. Let’s explore the differences in the following examples.

felines: List[str] = ["lion", "jaguar", "ocelot", "lynx", "panther", "tiger", "cheetah", "cougar"]
# Using .copy()

cats: List[str] = felines.copy()
print(cats)
['lion', 'jaguar', 'ocelot', 'lynx', 'panther', 'tiger', 'cheetah', 'cougar']
# Using =

cats1: List[str] = felines
print(cats1)
['lion', 'jaguar', 'ocelot', 'lynx', 'panther', 'tiger', 'cheetah', 'cougar']

Aren’t these the same though? Well, let’s see what happens if we change the original felines list. Do you think the copied lists will be affected or not when we apply the .append() method?

felines.append("domestic cat")

print("Copied List: ", cats)
print("Assigned List: ", cats1)
Copied List:  ['lion', 'jaguar', 'ocelot', 'lynx', 'panther', 'tiger', 'cheetah', 'cougar']
Assigned List:  ['lion', 'jaguar', 'ocelot', 'lynx', 'panther', 'tiger', 'cheetah', 'cougar', 'domestic cat']

Therefore, if you use the = operator, the new list will be updated every time the original list is updated (i.e., aliasing). However, if you use .copy(), the copied list becomes a separate object that will not be modified when the original list changes.

  1. The .extend() Method

You can also extend one list with another one by using .extend().

subjects1: List[str] = ['data science', 'computer science', 'programming', 'statistics']
subjects2: List[str] = ['software engineering', 'artificial intelligence']

subjects1.extend(subjects2)

print(subjects1)
print(subjects2)
['data science', 'computer science', 'programming', 'statistics', 'software engineering', 'artificial intelligence']
['software engineering', 'artificial intelligence']
:class: tip, dropdown
The <b>first print</b> will show <code>[1, 2, 3, 4, 5, 6]</code> since we extend list_1 with list_2. In other words, we add list_2 into the end of list_1. Notice, that here we did not create a new variable to store the extended list.<br>
    The <b>second print</b> will show <code>[4, 5, 6]</code> since that just prints list_2.<br>
    Then we have an aesthetic print :)<br>
    <b>Lastly, we print</b> <code>['a', 'b', 'c', 'd', 'e', 'f']</code>. Notice that in order to use the <code>+</code> operator, we assigned the value of <code>list_3 + list_4</code> to a new variable.
# Think before you run it :)

list_1: List[int] = [1, 2, 3]
list_2: List[int] = [4, 5, 6]

list_1.extend(list_2)
print(list_1)
print(list_2)

print('---------')

list_3: List[str] = ['a', 'b', 'c']
list_4: List[str] = ['d', 'e', 'f']

list_34 = list_3 + list_4
print(list_34)
[1, 2, 3, 4, 5, 6]
[4, 5, 6]
---------
['a', 'b', 'c', 'd', 'e', 'f']
  1. The .sort() Method

For some operations, it can be handy to have your list of elements sorted. To do this, you can use .sort().

When applied to a list of strings it uses alphabetical order and when the elements are integers or floats it goes from low to high.

a_list_of_num: List[int] = [8, 5, 7, 2, 9, 10]
a_list_of_num.sort()

print(a_list_of_num)
[2, 5, 7, 8, 9, 10]
subjects: List[str] = ['data science', 'computer science', 'programming', 'statistics', 'software engineering', 'artificial intelligence']
subjects.sort()

print(subjects)
['artificial intelligence', 'computer science', 'data science', 'programming', 'software engineering', 'statistics']

6.6. List Functions#

Now, we’ll explore some functions you can use on lists. We start with a list of numbers.

even_numbers: List[int] = [2, 4, 6, 8, 10]
  1. The sum() Function: adds up the elements of a list and returns the sum.

sum_even_numbers = sum(even_numbers)
print(sum_even_numbers)
30
  1. The len() Function: returns the length of the list.

len_even_numbers = len(even_numbers)
print(len_even_numbers)
5
  1. The min() Function: returns minimum number of a list.

min_even_numbers = min(even_numbers)
print(min_even_numbers)
2
  1. The max() Function: returns maximum number of a list.

max_even_numbers = max(even_numbers)
print(max_even_numbers)
10

6.7. List and Strings#

Although they may seem similar, a list of characters is not the same as a string.

You can use the list() to convert a string to a list. This will result in a list of individual characters as its elements.

ai_str: str = "Artificial Intelligence"
print(ai_str)
print(type(ai_str))
Artificial Intelligence
<class 'str'>
ai_list: List[str] = list(ai_str)
print(ai_list)
print(type(ai_list))
['A', 'r', 't', 'i', 'f', 'i', 'c', 'i', 'a', 'l', ' ', 'I', 'n', 't', 'e', 'l', 'l', 'i', 'g', 'e', 'n', 'c', 'e']
<class 'list'>

Note

As list is the name of a built-in function, it should be avoided as a variable name.

To convert a string to a list of words, you can use the .split() method.

claim: str = 'Data science is younger than computer science or not?'

words: List[str] = claim.split()
print(words)
['Data', 'science', 'is', 'younger', 'than', 'computer', 'science', 'or', 'not?']

You can also split on a specific character. To do this, you use an optional argument called the delimiter.

big_word: str = 'Multi-language-programming'

words: List[str] = big_word.split('-')
print(words)
['Multi', 'language', 'programming']

There is also an inverse of the split() operation, namely the join()function.

words: List[str] = ['Data', 'science', 'is', 'fun!']

sentence : str = ' '.join(words)
print(sentence)
Data science is fun!

6.8. Exercises#

Let’s practice! Mind that each exercise is designed with multiple levels to help you progressively build your skills. Level 1 is the foundational level, designed to be straightforward so that everyone can successfully complete it. In Level 2, we step it up a notch, expecting you to use more complex concepts or combine them in new ways. Finally, in Level 3, we get closest to exam level questions, but we may use some concepts that are not covered in this notebook. However, in programming, you often encounter situations where you’re unsure how to proceed. Fortunately, you can often solve these problems by starting to work on them and figuring things out as you go. Practicing this skill is extremely helpful, so we highly recommend completing these exercises.

For each of the exercises, make sure to add a docstring and type hints, and do not import any libraries unless specified otherwise.

6.8.1. Exercise 1#

Level 1: Write a Python function called reverse_string() that takes a string as input and returns the reverse of the string. Make sure to use the return statement. Additionally, print the result outside of the function.

Input example: you pass this in a function call.

input_string: str = "Python"

Output example:

"nohtyP"
# TODO.

Level 2: Modify the reverse_string function to create reverse_string_and_check_palindrome(). This new function should take a string as input, reverse it, and return True if the reversed string is a palindrome (reads the same forwards and backwards), and False otherwise. Use the return statement, and print the result outside of the function.

Input example: you pass this in a function call.

input_string: str = "racecar"

Output example:

True
# TODO.

Level 3: Modify the reverse_string_and_check_palindrome() function to:

  • ignore case sensitivity (Convert all letters to lowercase or uppercase to make comparison case-insensitive.)

  • and non-alphanumeric characters (Filter out characters like punctuation, spaces, and special symbols so that only letters and digits are considered.)

when checking if the input string is a palindrome.

Input example: you pass this in a function call.

input_string:str = "A man, a plan, a canal: Panama"

Output example:

True
# TODO.

6.8.2. Exercise 2#

Level 1: Create a Python function named find_longest_word() that accepts a sentence as input and returns the longest word within that sentence. Use a while loop (or a for loop if that’s more familiar to you). In case there are several words of the same maximum length, return the first one that appears. Remember to use the return statement to provide the result. Additionally, print the result outside of the function. Assume that words are separated by spaces, and there are no punctuation marks.

Example input: you pass this in a function call.

input_sentence: str = "The quick brown fox jumps over the lazy dog"

Example output:

"quick"
# TODO.

Level 2: Modify the find_longest_word() function to take a sentence as input and return a list of all the longest words in the sentence. If there are multiple words with the same longest length, include all of them in the list. Make sure to use the return statement. Additionally, print the result outside of the function.

Example input: you pass this in a function call.

input_sentence: str = "The quick brown fox jumps over the lazy dog"

Example output:

["quick", "brown"]
# TODO.

Level 3: Modify the find_longest_word() function to accept multiple sentences as input and return a dictionary where:

  • The keys are the sentences, and

  • The values are lists of all the longest words for each sentence (just like in Level 2).

Additional Constraints:

  • The function should handle cases where sentences have different numbers of words.

  • If multiple sentences have words of the same maximum length, list all such words for each sentence.

  • Ensure that punctuation is correctly handled (i.e., remove any punctuation marks from words before checking their lengths).

Example input: you pass this in a function call.

input_sentences: List[str] = [
    "The quick brown fox jumps over the lazy dog",
    "A faster fox and a smarter dog will win",
    "Some punctuation, like commas, need to be removed!"
]

Example output:

{
    "The quick brown fox jumps over the lazy dog": ["quick", "brown"],
    "A faster fox and a smarter dog will win": ["smarter", "faster"],
    "Some punctuation, like commas, need to be removed!": ["punctuation", "removed"]
}
# TODO.

6.8.3. Exercise 3#

Level 1: Write a Python function called find_median() that takes a list of sorted numbers as input and returns the median value.

The median is the middle value in a sorted list of numbers. If the list has an even number of elements, return the average of the two middle values. Make sure to use the return statement. Additionally, print the result outside of the function.

Example input: you pass this in a function call.

number_list: List[int] = [1, 2, 3, 6]

Example output:

2.5
# TODO.

Level 2: Modify the find_median() function and ensure that the function works correctly even if the input list is not sorted. Make sure to use the return statement. Additionally, print the result outside of the function.

Example input: you pass this in a function call.

number_list: List[int] = [3, 1, 6, 2]

Example output:

2.5
# TODO.

Level 3: Instead of using a built-in sorting function, implement your own sorting algorithm, namely BubbleSort. Write a second function named bubble_sort() and call it within find_median(). Before you implement BubbleSort, make sure to get familiar with looping over lists and the range() function. Make sure to use the return statement. Additionally, print the result outside of the function.

# TODO.

Material for the VU Amsterdam course “Introduction to Python Programming” for BSc Artificial Intelligence students. These notebooks are created using the following sources:

  1. Learning Python by Doing: This book, developed by teachers of TU/e Eindhoven and VU Amsterdam, is the main source for the course materials. Code snippets or text explanations from the book may be used in the notebooks, sometimes with slight adjustments.

  2. Think Python

  3. GeekForGeeks