Вложенные списки. Часть 2
----------------------------

1.  Создание вложенных списков
2.  [Считывание вложенных списков](#считывание-вложенных-списков)
3.  [Перебор и вывод элементов вложенного списка](#перебор-и-вывод-элементов-вложенного-списка)
4.  [Обработка вложенных списков](#обработка-вложенных-списков)
5.  [Задачи](./nested_lists_tasks.md)


**Аннотация.** Урок посвящен работе с вложенными списками.

Создание вложенных списков
--------------------------

Для создания вложенного списка можно использовать литеральную форму записи – перечисление элементов через запятую в квадратных скобках:

    my_list = [[0], [1, 2], [3, 4, 5]]

Иногда нужно создать вложенный список, заполненный по определенному правилу – шаблону. Например, список длиной `n`, содержащий списки длиной `m`, каждый из которых заполнен нулями.

Рассмотрим несколько способов решения задачи.

**Способ 1.** Создадим пустой список, потом `n` раз добавим в него новый элемент – список длины `m`, составленный из нулей:

    n, m = int(input()), int(input())    # считываем значения n и m
    my_list = []
    
    for _ in range(n):
        my_list.append([0] * m)
    
    print(my_list)

Если ввести значения `n = 3`, `m = 5`, то результатом работы такого кода будет:

    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

Если передать значения `n = 5`, `m = 3`, то результатом работы такого кода будет:

    [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

**Способ 2.** Сначала создадим список из `n` элементов (для начала просто из `n` нулей). Затем сделаем каждый элемент списка ссылкой на другой список из `m` элементов, заполненный нулями:

    n, m = int(input()), int(input())  # считываем значения n и m
    my_list = [0] * n
    
    for i in range(n):
        my_list[i] = [0] * m
    
    print(my_list)

**Способ 3.** Можно использовать генератор списка: создадим список из `n` элементов, каждый из которых будет списком, состоящих из `m` нулей:

    n, m = int(input()), int(input())  # считываем значения n и m
    
    my_list = [[0] * m for _ in range(n)]
    
    print(my_list)

В этом случае каждый элемент создается независимо от остальных (заново конструируется вложенный список `[0] * m` для заполнения очередного элемента списка).

Обратите внимание, что очевидное решение, использующее операцию умножения списка на число (операция повторения), оказывается неверным:

    n, m = int(input()), int(input())  # считываем значения n и m
    
    my_list = [[0] * m ] * n
    
    print(my_list)

В этом легко убедиться, если присвоить элементу `my_list[0][0]` любое значение, например, `17`, а затем вывести список на печать:

    n, m = int(input()), int(input())
    
    my_list = [[0] * m ] * n
    my_list[0][0] = 17
    
    print(my_list)

Если ввести значения `n = 5`, `m = 3`, то результатом работы такого кода будет:

    [[17, 0, 0], [17, 0, 0], [17, 0, 0], [17, 0, 0], [17, 0, 0]]

То есть, изменив значение элемента списка `my_list[0][0]`, мы также изменили значения элементов `my_list[1][0]`, `my_list[2][0]`, `my_list[3][0]`, `my_list[4][0]`.

Причина такого поведения кроется в самой природе списков (тип  `list`). В Python списки – ссылочный тип данных. Конструкция `[0] * m` возвращает **ccылку** на список из `m` нулей. Повторение этого элемента создает список из `n` ссылок на один и тот же список.

Вложенный список нельзя создать при помощи операции повторения (умножения списка на число). Для корректного создания вложенного списка мы используем способы 1−31-3, отдавая предпочтение способу 33.

### Считывание вложенных списков
----------------------------

Если элементы списка вводятся через клавиатуру (каждая строка на отдельной строке, всего `n` строк, числа в строке разделяются пробелами), для ввода списка можно использовать следующий код:

    n = 4                                         # количество строк (элементов)
    my_list = []
    
    for _ in range(n):
        elem = [int(i) for i in input().split()]  # создаем список из элементов строки
        my_list.append(elem)

В этом примере мы используем списочный метод `append()`, передавая ему в качестве аргумента другой список. Так у нас получается список списков.

В результате, если на вход программе подаются строки:

    2 4
    6 7 8 9
    1 3
    5 6 5 4 3 1

то в переменной `my_list` будет храниться список:

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

Не забывайте, что метод `split()` возвращает **список строк, а не чисел**. Поэтому мы предварительно сконвертировали строку в число, с помощью вызова функции `int()`.

Также следует помнить отличие работы списочных методов `append()` и `extend()`.

Следующий код:

    n = 4
    my_list = []
    
    for _ in range(n):
        elem = [int(i) for i in input().split()]
        my_list.extend(elem)

создает одномерный **(!)** список, а не вложенный. В переменной `my_list` будет храниться список:

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

### Перебор и вывод элементов вложенного списка
-------------------------------------------

Как мы уже знаем, для доступа к элементу списка указывают индекс этого элемента в квадратных скобках. В случае двумерных вложенных списков надо указать два индекса (каждый в отдельных квадратных скобках), в случае трехмерного списка — три индекса и т. д.

Рассмотрим программный код:

    my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    print(my_list[0][0])
    print(my_list[1][2])
    print(my_list[2][1])

Результатом работы такого кода будет:

    1
    6
    8

Когда нужно перебрать все элементы вложенного списка (например, чтобы вывести их на экран), обычно используются **вложенные циклы**.

Рассмотрим программный код:

    my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    for i in range(len(my_list)):
        for j in range(len(my_list[i])):
            print(my_list[i][j], end=' ')  # используем необязательный параметр end
        print()                            # перенос на новую строку

 Результатом работы такого кода будет:

    1 2 3 
    4 5 6 
    7 8 9 

Вызов функции `print()` с пустыми параметрами нужен для того, чтобы переносить вывод на новую строку, после того как будет распечатан очередной элемент (список) вложенного списка.

В предыдущем примере мы перебирали **индексы элементов**, а можно сразу перебирать сами элементы вложенного списка:

    my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    for row in my_list:
        for elem in row:
            print(elem, end=' ')
        print()

 Результатом работы такого кода будет:

    1 2 3 
    4 5 6 
    7 8 9 

Перебор элементов вложенного списка по индексам дает нам больше гибкости для вывода данных. Например, поменяв порядок переменных `i` и `j`, мы получаем иной тип вывода:

    my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    for i in range(len(my_list)):
        for j in range(len(my_list[i])):
            print(my_list[j][i], end=' ')  # выводим my_list[j][i] вместо my_list[i][j]
        print()

 Результатом работы такого кода будет:

    1 4 7 
    2 5 8 
    3 6 9 

### Обработка вложенных списков
---------------------------

Для обработки элементов вложенного списка, так же как и для вывода его элементов на экран, как правило, используются **вложенные циклы**.

Используем вложенный цикл для подсчета суммы всех чисел в списке:

    my_list = [[1, 9, 8, 7, 4], [7, 3, 4], [2, 1]]
    
    total = 0
    for i in range(len(my_list)):
        for j in range(len(my_list[i])):
            total += my_list[i][j]
    
    print(total)

Или то же самое с циклом не по индексу, а по значениям:

    my_list = [[1, 9, 8, 7, 4], [7, 3, 4], [2, 1]]
    
    total = 0
    for row in my_list:
        for elem in row:
            total += elem
    
    print(total)

Таким образом, можно обработать элементы вложенного списка практически в любом языке программирования. В Python, однако, можно упростить код, если использовать встроенную функцию `sum()`, которая принимает список чисел и возвращает его сумму. Подсчет суммы с помощью функции `sum()` выглядит так:

    my_list = [[1, 9, 8, 7, 4], [7, 3, 4], [2, 1]]
    
    total = 0
    for row in my_list:  # в один цикл
        total += sum(row)
    print(total)

Названия переменных `row` (строка) и `elem` (элемент) удобно использовать при переборе вложенного списка по значениям. Названия переменных `i` и `j` используются при переборе вложенного списка по индексам.

---------------------------