Python — Collections

Python — Collections

vvrubel

Track content


List

In your programs, you often need to group several elements in order to process them as a single object. For this, you will need to use different collections. One of the most useful collections in Python is a list. It is one of the most important things in Python.

§1. Creating and printing lists

Look at a simple list that stores several names of dogs' breeds:

dog_breeds = ['corgi', 'labrador', 'poodle', 'jack russell']
print(dog_breeds)  # ['corgi', 'labrador', 'poodle', 'jack russell']

In the first line, we use square brackets to create a list that contains four elements and then assign it to the dog_breeds variable. In the second line, the list is printed through the variable's name. All the elements are printed in the same order as they were stored in the list because lists are ordered.

Here is another list that contains five integers:

numbers = [1, 2, 3, 4, 5]
print(numbers)  # [1, 2, 3, 4, 5]

Another way to create a list is to invoke the list function. It is used to create a list out of an iterable object: that is, a kind of object where you can get its elements one by one. The concept of iterability will be explained in detail further on, but let's look at the examples below:

list_out_of_string = list('danger!')
print(list_out_of_string)  # ['d', 'a', 'n', 'g', 'e', 'r', '!']

list_out_of_integer = list(235)  # TypeError: 'int' object is not iterable

So, the list function creates a list containing each element from the given iterable object. For now, remember that a string is an example of an iterable object, and an integer is an example of a non-iterable object. A list itself is also an iterable object.

Let's also note the difference between the list function and creating a list using square brackets:

multi_element_list = list('danger!')
print(multi_element_list)  # ['d', 'a', 'n', 'g', 'e', 'r', '!']

single_element_list = ['danger!']
print(single_element_list)  # ['danger!']

The square brackets and the list function can also be used to create empty lists that do not have elements at all.

empty_list_1 = list()
empty_list_2 = []

In the following topics, we will consider how to fill empty lists.

§2. Features of lists

Lists can store duplicate values as many times as needed.

on_off_list = ['on', 'off', 'on', 'off', 'on']
print(on_off_list)  # ['on', 'off', 'on', 'off', 'on']

Another important thing about lists is that they can contain different types of elements. So there are neither restrictions, nor fixed list types, and you can add to your list any data you want, like in the following example:

different_objects = ['a', 1, 'b', 2]

§3. Length of a list

Sometimes you need to know how many elements are there in a list. There is a built-in function called len that can be applied to any iterable object, and it returns simply the length of that object

So, when applied to a list, it returns the number of elements in that list.

numbers = [1, 2, 3, 4, 5]
print(len(numbers))  # 5

empty_list = list()
print(len(empty_list))  # 0

single_element_list = ['danger!']
print(len(single_element_list))  # 1

multi_elements_list = list('danger!')
print(len(multi_elements_list))  # 7

In the example above, you can see how the len() function works. Again, pay attention to the difference between list() and [] as applied to strings: it may not result in what you expected.

§4. Recap

As a recap, we note that lists are:

  • ordered, i.e. each element has a fixed position in a list;
  • iterable, i.e. you can get their elements one by one;
  • able to store duplicate values;
  • able to store different types of elements.

Up!

Indexes

There are several types of collections to store data in Python. Positionally ordered collections of elements are usually called sequences, and both lists and strings belong to them. Each element in a list, as well as each character in a string, has an index that corresponds to its position. Indexes are used to access elements within a sequence. Indexing is zero-based, so if you see a person who counts from zero, you must have met a programmer.

§1. Indexes of elements

To access an element of a list by its index, you need to use square brackets. You add the brackets after the list and, between them, you write the index of an element you want to get.

Don't forget, the indexes start at 0, so the index of the first element is 0. The index of the last element is equal to len(list) - 1.

Let's take a look at the example below:

colors = ['red', 'green', 'blue']

first_elem = colors[0]   # 'red'
second_elem = colors[1]  # 'green'
third_elem = colors[2]   # 'blue'

Strings work in the same way:

pet = "cat"

first_char = pet[0]   # 'c'
second_char = pet[1]  # 'a'
third_char = pet[2]   # 't'

§2. Potential pitfalls

When using indexes, it's important to stay within the range of your sequence: you'll get an error (called IndexError) if you try to access an element with a non-existing index!

colors = ['red', 'green', 'blue']
pet = "cat"

print(colors[3])  # IndexError: list index out of range
print(pet[3])     # IndexError: string index out of range

There is one more obstacle in your way. Imagine that you want to change one of the elements in a list. It can be easily done:

colors = ['red', 'green', 'blue']

colors[1] = 'white'
print(colors)  # ['red', 'white', 'blue']

However, when it comes to strings, such reassignment is impossible. Strings, unlike lists, are immutable, so you can't modify their contents with indexes:

pet = "cat"

pet[0] = "b"
# TypeError: 'str' object does not support item assignment

Don't worry, after some practice, you will not encounter these errors.

§3. Negative indexes

The easier way to access the elements at the end of a list or a string is to use negative indexes: the minus before the number changes your perspective in a way and you look at the sequence from the end. So, the last element of a list, in this case, has the index equal to -1, and the first element of the list has the index -len(list) (the length of the list).

For example:

colors = ['red', 'green', 'blue']

last_elem = colors[-1]    # 'blue'
second_elem = colors[-2]  # 'green'
first_elem = colors[-3]   # 'red'

pet = "cat"

last_char = pet[-1]    # 't'
second_char = pet[-2]  # 'a'
first_char = pet[-3]   # 'c'

As you can see, it works the same for lists and strings.

If you write a non-existing negative index, you'll also get IndexError. Be careful with indexes to avoid off-by-one errors in your code.

The following picture shows the general concept of indexes in a list:


Since you have learned the concept of indexes, we hope that from now on you will not encounter any difficulties when using them!


Up!

Set

When you need to get rid of duplicates in a sequence or intend to perform some mathematical operations, you may use a set object. A set is an unordered container of hashable objects. You will learn more about hashable objects later, for now, remember that only immutable data types can be elements of a set. Due to their form, sets do NOT record element position or order of insertion, so you cannot retrieve an element by its index.

§1. Creating sets

First things first, we create a set by listing its elements in curly braces. The only exception would be an empty set that can be formed with the help of a set()function:

empty_set = set()
print(type(empty_set))   # <class 'set'>

empty_dict = {}
print(type(empty_dict))  # <class 'dict'>

If you pass a string or a list into set(), the function will return a set consisting of all the elements of this string/list:

flowers = {'rose', 'lilac', 'daisy'}

# the order is not preserved
print(flowers)  # {'daisy', 'lilac', 'rose'}  


letters = set('philharmonic')
print(letters)  # {'h', 'r', 'i', 'c', 'o', 'l', 'a', 'p', 'm', 'n'}

Each element is considered a part of a set only once, so double letters are counted as one element:

letters = set('Hello')
print(len(letters))  # the length equals 4
print(letters)       # {'H', 'e', 'o', 'l'}

Moreover, using sets can help you avoid repetitions:

states = ['Russia', 'USA', 'USA', 'Germany', 'Italy']
print(set(states))  # {'Russia', 'USA', 'Italy', 'Germany'}

Have a look: as the order of naming the elements doesn't play any role, the following two sets will be equal.

set1 = {'A', 'B', 'C'}
set2 = {'B', 'C', 'A'}
print(set1 == set2)  # True

§2. Working with a set’s elements

You can:

  • get the number of set's elements with the help of len() function.
  • go through all the elements using for loop.
  • check whether an element belongs to a specific set or not (in / not in operators), you get the boolean value.
nums = {1, 2, 2, 3}
print(1 in nums, 4 not in nums)  # True True
  • add a new element to the set with add() method or update() it with another collection
nums = {1, 2, 2, 3}
nums.add(5)
print(nums)  # {1, 2, 3, 5}

another_nums = {6, 7}
nums.update(another_nums)
print(nums)  # {1, 2, 3, 5, 6, 7}
 
# we can also add a list
text = ['how', 'are', 'you']
nums.update(text)
print(nums)  # {'you', 1, 2, 3, 5, 6, 7, 'are', 'how'}
 
# or a string
word = 'hello'
nums.add(word)
print(nums)  # {1, 2, 3, 'how', 5, 6, 7, 'hello', 'you', 'are'}
Note that when we update a set with a list, those are the elements of the list that are added to the set rather than the list itself.
  • delete an element from a specific set using discard/remove methods. The only difference between them operating is a situation when the element to be removed is absent from this set. In this case, discard does nothing and remove generates a KeyError exception.
nums.remove(2)
print(nums)  # {1, 3, 5}

empty_set = set()
empty_set.discard(2)  # nothing happened
empty_set.remove(2)   # KeyError: 2
  • remove one random element using pop() method. As it's going to be random, you don't need to choose an argument.
nums = {1, 2, 2, 3}
nums.pop()
print(nums)  # {2, 3}
  • delete all elements from the set with clear() method.

§3. When to use sets?

One important feature of sets (and all unordered collections in general) is that they allow you to run membership tests much faster than lists. In real life, if you have a list and you try to check by hand whether a particular item is present there, the only way to do this is to look through the entire list until you find this item. Python does the same thing: it looks for the needed item starting from the beginning of a list, because it has no idea where it may be placed. If the item is located at the end or there is no such item at all, Python will iterate over the majority of items in the list by the time it discovers this fact. So, in case your program is looking for items in a large list many times, it will be slow.

And that's where sets come to help us! In sets membership testing works almost instantly, since they use a different way of storing and arranging values. So, depending on the situation, you need to decide what is more important to you: preserving the order of items in your collection or testing for membership in a faster way. In the first case, it's reasonable to store your items in the list, in the second it's better to use set.

§4. Frozenset

The only difference between set and frozenset is that set is a mutable data type, but frozenset is not. To create a frozenset, we use the frozenset() function.

empty_frozenset = frozenset()
print(empty_frozenset)  # frozenset()

We can also create a frozenset from a list, string or set:

frozenset_from_set = frozenset({1, 2, 3})
print(frozenset_from_set)  # frozenset({1, 2, 3})

frozenset_from_list = frozenset(['how', 'are', 'you'])
print(frozenset_from_list)  # frozenset({'you', 'are', 'how'})

As mentioned above, a frozenset is immutable. This means that while the elements of a set can change, in a frozenset they remain unchanged after creation. You can not add or remove items.

empty_frozenset.add('some_text')  # AttributeError: 'frozenset' object has no attribute 'add'

So why do we need frozenset exactly? Since a set is mutable, we can not make it an element of another set.

text = {'hello', 'world'}
nested_text = {'!'}
nested_text.add(text)  # TypeError: unhashable type: 'set'

But with a frozenset, such problems will not appear. It can be an element of another set or an element of another frozenset due to its hashability and immutability.

some_frozenset = frozenset(text)
nested_text.add(some_frozenset)
print(nested_text)  # {'!', frozenset({'world', 'hello'})}

Also, these properties of frozensets allow them to be keys in a Python dictionary, but you will learn more about this later.

§5. Conclusions

All things considered, now you know how to work with sets:

  • you know how to create a new set and what can be stored in a set (immutable data types only).
  • you understand the difference between the set and other Python objects.
  • you can work with a set's elements: add new elements or delete them, differentiate discard and remove methods, etc.
  • you know when to use sets (this really can save your time!).
  • you know that frozenset is an immutable alternative of set.

Up!

Tuple

By now, you definitely know how to handle a list, the most popular collection in Python. Now let's discover an equally useful data type — tuples. You should remember that they are almost identical to lists. What sets them apart is their immutability.

§1. Define a tuple

Since tuples cannot be changed, tuple creation is similar to opening a box of a fixed size, then putting several values into this box and sealing it. Once the box has been sealed, you cannot modify its size or content.

Use a pair of parentheses to define a tuple:

empty_tuple = ()
print(type(empty_tuple))  # <class 'tuple'>

Empty tuples are easy to create. Then what went wrong in the following example?

not_a_tuple = ('cat')
print(not_a_tuple)        # 'cat'
print(type(not_a_tuple))  # <class 'str'>

As you can see, the variable we created stores a string. It's actually a comma that makes a tuple, not parentheses. Let's fix this piece of code:

now_a_tuple = ('cat',)
print(now_a_tuple)        # ('cat',)
print(type(now_a_tuple))  # <class 'tuple'>

So, always use a comma when defining a singleton tuple. In fact, even if your tuple contains more than one element, separating items with commas will be enough:

weekend = 'Saturday', 'Sunday'
print(weekend)        # ('Saturday', 'Sunday')
print(type(weekend))  # <class 'tuple'>

The built-in function tuple() turns strings, lists and other iterables into a tuple. With this function, you can create an empty tuple as well.

# another empty tuple
empty_tuple = tuple()
print(empty_tuple)        # ()
print(type(empty_tuple))  # <class 'tuple'>

# a list turned into a tuple
bakers_dozen = tuple([12, 1])
print(bakers_dozen == (12, 1))  # True

# a tuple from a string
sound = tuple('meow')
print(sound)  # ('m', 'e', 'o', 'w')

§2. What can we do with tuples?

First, let's examine what characteristics lists and tuples have in common.

Both lists and tuples are ordered, that is, when passing elements to these containers, you can expect that their order will remain the same. Tuples are also indifferent to the nature of data stored in them, so you can duplicate values or mix different data types:

tiny_tuple = (0, 1, 0, 'panda', 'sloth')

print(len(tiny_tuple))  # 5
print(tiny_tuple)       # (0, 1, 0, 'panda', 'sloth')

Just like lists, tuples support indexing. Be careful with indexes though, if you want to get along without IndexErrors.

empty_tuple = ()
print(empty_tuple[0])  # IndexError

numbers = (0, 1, 2)
print(numbers[0])   # 0
print(numbers[1])   # 1
print(numbers[2])   # 2
print(numbers[3])   # IndexError

And here the first distinctive feature of tuples comes into play. What they don't support is item assignment. While you can change an element in a list referring to this element by its index, it's not the case for tuples:

# ex-capitals
capitals = ['Philadelphia', 'Rio de Janeiro', 'Saint Petersburg']

capitals[0] = 'Washington, D.C.'
capitals[1] = 'Brasília'
capitals[2] = 'Moscow'
print(capitals)  # ['Washington, D.C.', 'Brasília', 'Moscow']

former_capitals = tuple(capitals)
former_capitals[0] = 'Washington, D.C.'  # TypeError

In the example above, we tried to update the tuple and it didn't end well. You can't add an item to a tuple or remove it from there (unless you delete the entire tuple). However, immutability has a positive side. We'll discuss it in the next section.

§3. Immutability and its advantages

By this time, one question might have come to your mind: why use tuples when we have lists? Predictably, all answers conduce to immutability. Let's dwell on its upsides:

  • Tuples are faster and more memory-efficient than lists. Whenever you need to work with large amounts of data, you should give it a thought. If you are not going to modify your data, perhaps you should decide on tuples.
  • A tuple can be used as a dictionary key, whereas lists as keys will result in TypeError.
  • Last but not least, it's impossible to change by accident the data stored in a tuple. It may prove a safe and robust solution to some tasks.

§4. Summary

Those were the very basics of tuples in Python. Just like lists, tuples are ordered and iterable. Unlike lists, they are immutable. You'll learn more of tuple features in the next topics, now it's time to write your first programs with them!


Up!

Operations with list

You already know how to create lists (even empty ones), so, no wonder, that you may want to change your lists somehow. There are lots of things to do with a list, you can read about them in the Python Data Structures documentation. In this topic, we will discuss only the basic functions.

§1. Adding one element

A list is a dynamic collection, it means you can add and remove elements. To take a closer look, let's create an empty list of dragons.

dragons = []  # we do not have dragons yet

What is next? The first thing that comes to mind is, of course, to add new elements to the list.

To add a new element to the end of an existing list, you need to invoke the list.append(element) function. It takes only a single argument, so this way you can add only one element to the list at a time.

dragons.append('Rudror')
dragons.append('Targiss')
dragons.append('Coporth')

Now you have three dragons, and they are ordered the way you added them:

print(dragons)  # ['Rudror', 'Targiss', 'Coporth']

§2. Adding several elements

There is the list.extend(another_list) operation that adds all the elements from another iterable to the end of a list.

numbers = [1, 2, 3, 4, 5]
numbers.extend([10, 20, 30])
print(numbers)  # [1, 2, 3, 4, 5, 10, 20, 30]

Be careful — if you use list.append(another_list) instead of list.extend(another_list), it adds the entire list as an element:

numbers = [1, 2, 3, 4, 5]
numbers.append([10, 20, 30])
print(numbers)  # [1, 2, 3, 4, 5, [10, 20, 30]]

Alternatively, to merge two lists, you can just add one to another:

numbers_to_four = [0, 1, 2, 3, 4]
numbers_from_five = [5, 6, 7, 8, 9]
numbers = numbers_to_four + numbers_from_five 
print(numbers)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

If you need a list with repeating elements, you can create a list with the repeating pattern, and then just multiply it by any number. This is particularly useful when you want to create a list of a specific length with the same value:

pattern = ['a', 'b', 'c']
patterns = pattern * 3
print(patterns)  # ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']

one = [1]
ones = one * 7
print(ones)  # [1, 1, 1, 1, 1, 1, 1]

§3. Removing elements

The opposite of adding elements — deleting them — can be done in three ways. Let's have a look at them.

First, we can use the list.remove(element) operation.

dragons.remove('Targiss')
print(dragons)  # ['Rudror', 'Coporth']

If the element we want to delete occurs several times in the list, only the first instance of that element is removed.

dragons = ['Rudror', 'Targiss', 'Coporth', 'Targiss']
dragons.remove('Targiss')
print(dragons)  # ['Rudror', 'Coporth', 'Targiss']

The other two ways remove elements by their indexes rather than the values themselves. The del keyword deletes any kind of objects in Python, so it can be used to remove specific elements in a list:

dragons = ['Rudror', 'Targiss', 'Coporth']
del dragons[1]
print(dragons)  # ['Rudror', 'Coporth']

Finally, there is the list.pop() function. If used without arguments, it removes and returns the last element in the list.

dragons = ['Rudror', 'Targiss', 'Coporth']
last_dragon = dragons.pop()
print(last_dragon)  # 'Coporth'
print(dragons)      # ['Rudror', 'Targiss']

Alternatively, we can specify the index of the element we want to remove and return:

dragons = ['Rudror', 'Targiss', 'Coporth']
first_dragon = dragons.pop(0)
print(first_dragon)  # 'Rudror'
print(dragons)       # ['Targiss', 'Coporth']

§4. Inserting elements at a specified position

At the beginning of this topic, we have learned how to add new elements to the end of a list. If we want to add a new element in the middle, we use the list.insert(position, element) operation. The first argument is the index of the element before which the new element is going to be inserted; so list.insert(0, element) inserts an element to the beginning of the list, and list.insert(len(list), element) is completely similar to list.append(element).

Here is an example:

years = [2016, 2018, 2019]
years.insert(1, 2017)           # [2016, 2017, 2018, 2019]
years.insert(0, 2015)           # [2015, 2016, 2017, 2018, 2019]
years.insert(len(years), 2020)  # [2015, 2016, 2017, 2018, 2019, 2020]

Now, you can fill any empty list with something useful!

§5. Membership testing in a list

Another thing that can be quite useful is checking if an item is present in the list. It can be done simply by using in and not in operators:

catalog = ['yogurt', 'apples', 'oranges', 'bananas', 'milk', 'cheese']
 
print('bananas' in catalog)      # True
 
product = 'lemon'
print(product in catalog)        # False
print(product not in catalog)    # True

§6. Searching specific elements

Sometimes, knowing that the specified element is in the list is not enough; we may want to get more information about it — how many times the element occurs in the list and at which position.

The method count() can help with the quantity:

grades = [10, 5, 7, 9, 5, 10, 9]
print(grades.count(5))  # 2

We can use the method index() to get the position of the element. It finds the index of the first occurrence of the element in the list:

print(grades.index(7))   # 2
print(grades.index(10))  # 0

We can also specify the interval for searching: list.index(element, start, end).

print(grades.index(9, 2, 5))  # 3

# if we don't specify the end of the interval, it automatically equals the end of the list
print(grades.index(10, 1))    # 5

Be careful — the end index is not included in the interval.

It is also good to know that if the element we are looking for is not in the list, the method will cause an error:

print(grades.index(8))  # ValueError: 8 is not in list

Our discussion of the basic operations with lists has come to its end. If you need more information, check out the Python Data Structures documentation.


Up!

List comprehension

List comprehension is a way of making new lists. It allows you to create a list from any iterable object in a concise and efficient manner. See the basic syntax below:

# list comprehension syntax
new_list = [x for x in some_iterable]

Here you can see that list comprehension is specified by square brackets (just like the list itself) inside which you have a for loop over some iterable object. In our example, new_list will simply consist of all elements from some_iterable object. The code above is completely equivalent to the one below, however, it takes less space and works a little bit faster!

# the equivalent code
new_list = []
for x in some_iterable:
    new_list.append(x)

You may wonder why there is a need for list comprehensions at all since we have a list() function. Obviously, list comprehensions are used not just for copying elements from some iterable into a list, but mainly for modifying them in some way to create a specific new list. In this case, in the first place of the list comprehension, we write some function of our variable. For example, the code below shows how to create a list of squared numbers.

# squared numbers
numbers = [1, 2, 3]
square_list = [x * x for x in numbers]  # [1, 4, 9]

Also, we can use list comprehensions to convert elements of a list from one data type to another:

# from string to float
strings = ["8.9", "6.0", "8.1", "7.5"]
floats = [float(num) for num in strings]  # [8.9, 6.0, 8.1, 7.5]

§1. List comprehension with if

Another way to modify the original iterable object is by introducing the if statement into the list comprehension. The basic syntax is this:

# list comprehension with condition
new_list = [x for x in some_iterable if condition]

The conditional statement allows you to filter the elements of the original collection and work only with the elements you need. The if statement works here as a part of a comprehension syntax. The filtering condition is not an obligatory part, but it can be very useful. For instance, here it is used to create a list of odd numbers from another list:

# odd numbers
numbers = [4, 8, 15, 16, 23, 42, 108]
odd_list = [x for x in numbers if x % 2 == 1]  # [15, 23]

You can also modify the condition by using standard methods. For instance, if you want to create a list of words that end in -tion, you can do it like this:

# conditions with functions
text = ["function", "is", "a", "synonym", "of", "occupation"]
words_tion = [word for word in text if word.endswith("tion")]  
print(words_tion)  # ["function", "occupation"]

Finally, we can introduce the else statement in list comprehension. The syntax here differs a bit: [x if condition else y for x in some_iterable]. Using this, we can, for example, get 0 in a new list for each negative number in the old list:

old_list = [8, 13, -7, 4, -9, 2, 10]
new_list = [num if num >= 0 else 0 for num in old_list]
print(new_list)  # [8, 13, 0, 4, 0, 2, 10]

§2. Recap

In general, list comprehension should be used with caution: where it gains in efficiency it loses in readability. Nevertheless, it is a very useful tool and we hope you'll use it in your programs!


Up!

Nested lists

A list in Python may contain any objects as its elements, including other lists – they are called nested lists. Here is a couple of examples of nested lists:

nested_letters  = ['a', 'b', ['c', 'd'], 'e']
nested_numbers = [[1], [2], [3]]

§1. Accessing elements of nesting lists

Like with regular lists, elements of a nested list can be accessed by indexes. Note that the nested list still counts as a single element in its parent list.

numbers = [1, [2, 3], 4]
nested_numbers = numbers[1]

print(nested_numbers)     # [2, 3]
print(nested_numbers[1])  # 3

In this example, we obtain the second element of numbers by index 1. This element is the nested list [2, 3]. Then we print the second element of nested_numbers by index 1. This element is 3.

It is also possible to access an element of a nested list without an additional variable using a sequence of square brackets.

lists = [0, [1, [2, 3]]]
print(lists[1][1][0])   # 2

Basically, we go deeper from the outer list to the innermost when indexing. Naturally, if we ask for an element at the level that doesn't exist, we'll get an error:

print(lists[1][1][0][1])  # TypeError: 'int' object is not subscriptable

Just as if we were accessing an element that doesn't exist, at the level that does exist:

print(lists[3])  # IndexError: list index out of range

§2. Matrices

Nested lists are a convenient way to represent a matrix. For example, the matrix

simple matrix


might be represented as:

M = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Note that in such a list lengths of all nested lists must be the same, and they are equal to the dimension of the matrix.

When we want to extract an element from the matrix, e.g. element M[1][2] = 6, the first index selects the row, and the second index selects the column. However, as you know, it differs from mathematical representation in that numbering here starts from zero, rather than from one.

§3. Nested list comprehension

To iterate over nested lists, we can use nested list comprehensions. It is basically a combination of two or more list comprehensions and it is quite similar to nested "for" loops. To illustrate basic syntax for this kind of comprehension, let's consider an example.

Imagine a school as a list of classes that are, in turn, lists of students (their names).

# original list
school = [["Mary", "Jack", "Tiffany"], 
          ["Brad", "Claire"],
          ["Molly", "Andy", "Carla"]]

If you want to create a list of all students in all classes without the list comprehension it would look like this:

student_list = []
for class_group in school:
    for student in class_group:
        student_list.append(student)

Alternatively, we can also use a comprehension with a double for loop, then it would look like this:

student_list = [student for class_group in school for student in class_group]

In both cases the result is going to be the same:

print(student_list)
# result: ["Mary", "Jack", "Tiffany", "Brad", "Claire", "Molly", "Andy", "Carla"]

In this case, the order of the for loops is the same as in the notation with indentation: first the outer loop, and then the inner loop.

However, such a method may be less clear than the one without list comprehension, especially in cases when we need to use more than two for loops: it can make the code unreadable and counter-intuitive.

Consider the following line of code:

matrix = [[j for j in range(5)] for i in range(2)]

It’s not that easy to understand what the created matrix will look like. Compare to when we will put it this way:

matrix = [] 
  
for i in range(2): 
      
    # create empty row (a sublist inside our list)
    matrix.append([]) 
      
    for j in range(5): 
        matrix[i].append(j) 

It is much more readable, and now it is clear that the matrix will look like:

matrix = [[0, 1, 2, 3, 4],
          [0, 1, 2, 3, 4]]

It's important to bear in mind that shorter code does not imply better one, so you shouldn’t misuse list comprehension when working with nested lists.

Lists as such are a very useful type of container in Data Structures, and now you know how to store multiple inner lists inside an outer one, how to reach them and work with them.



Up!

Report Page