§9 Списки (Lists). Цикл for по коллекции. Генераторы. Двумерные списки. Срезы

Списки

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

  • Списки (Lists)
  • Кортежи (Tuples)
  • Array
  • Словарь (Dict)
  • Строка (Str)
  • Байт-массив (Bytearray)
  • Множества (Set)
Списки принадлежат к изменяемым типам данных. Список может содержать любое количество любых объектов, в том числе и вложенные списки. Список, как и другие коллекции, имеет общее имя для всех входящих в него элементов. На идентификаторы списков распространяются те же правила, что и на имена обычных переменных. Каждый элемент списка имеет номер (индекс), первый элемент имеет индекс [0]. Для того, чтобы создать список, необходимо перечислить данные через запятую и заключить их в квадратные скобки.
Программа 9.1

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

Здесь L — имя списка. Теперь обращаться к данным в списке можно по имени и индексу:

print("L[0] =", L[0], "\nL[5] =", L[5])
L[0] = 5 
L[5] = 1

Для создания пустого списка необходимо выполнить следующую инструкцию L = [].
Для получения элементов списка (L) на основе строки (в виде слов) можно воспользоваться функцией split():

L = input().split()

Если функция split() не имеет аргументов, то возвращается список, элементы которого были разделены пробелами. Если же используется иной разделитель, то он должен передаваться как аргумент, например split(".").
Примечание. Выражение input().split() возвращает список строк. Извлекая числа из данного списка, не забывайте использовать функции int() или float() для преобразования данных str -> int или str -> float. Для получения действительно числового списка необходимо использовать инструкцию с функциями map() и list() (см. программу 9.2).
Постановка задачи. Дан список. Найти в списке минимальный и максимальный элементы. Выполнить сортировку элементов списка.
Программа 9.2

L = list(map(float, input().split()))
print("min =", min(L), "\nmax =", max(L))
L.sort()
i = 0
while(i < len(L)):
    print(L[i], end=' ')
    i += 1
1.0 2.3 1.2 4.4 3.2 5.1 2.5     
min = 1.0 
max = 5.1
1.0 1.2 2.3 2.5 3.2 4.4 5.1

Программа выведет элементы исходного и упорядоченного списка, а также максимальный и минимальный элементы. Для определения максимального и минимального значения в списке используются стандартные функции max() и min().
Примечание. Обратите внимание, что функцию split() можно применить только к строке. Поэтому, в этой программе, используются функции map() и list(). Функция map() позволяет обрабатывать одну или несколько последовательностей с помощью заданной функции (в данном случае, int() будет выполняться над всеми элементами, полученными split()). Функция list() возвратит эту последовательность, как список. Эту инструкцию можно было бы заменить следующим кодом:

L = []
n = int(input())
while(n > 0):
        L.append(int(input()))
        n -= 1 

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

Цикл for по коллекции

В предыдущих примерах мы производили перебор элементов последовательности с помощью цикла while, но, в python, наиболее для этого приспособлен цикл for, который имеет форму цикла for по коллекции. Если нужно обращаться к элементам списка по индексу, то используется форма for с функцией range(). Приведем оба варианта вывода элементов списка (L) с помощью for основанного на range и цикла for по коллекции.
Программа 9.3

L = [1, 2, 3, 4, 5]
for i in range(len(L)):
    print(L[i], end=' ')

Программа 9.4

L = [1, 2, 3, 4, 5]
for elem in L:
    print(elem, end=' ')

Результат работы программы 9.3 и 9.4 одинаков — будут выведены элементы списка в одну строку с разделителем «пробел»:

1 2 3 4 5

В программе 9.3 используется функция len(), которая возвращает длину списка. В теле цикла элементы списка будут выводиться путем обращения по индексу. Похожим образом выглядят циклы for и в других языках программирования (например С, С++ и Pascal).
Иначе дело обстоит в программе 9.4. Здесь цикл for используется по своему прямому назначению в python — обходу элементов последовательности: вместо индексов в цикле перебираются сами элементы списка (на которые указывает переменная elem).
Часто требуется получить случайные числа в списке. Для этого нужно воспользоваться уже известными нам модулем random и методом append().
Программа 9.5

from random import randint
L = []
n = int(input("Введите размер списка\nn = "))
for i in range(n):
    L.append(randint(10, 99))
for elem in L:
    print(elem, end=' ')
Введите размер списка
n = 10
94 57 74 33 18 42 29 55 22 29

Генераторы списков

Генераторы позволяют в более компактной форме создать список. Например, программу 9.5 можно было бы переписать следующим образом:
Программа 9.6

from random import randint
L = [randint(10, 99) for i in range(10)]
print(L)
[18, 86, 26, 76, 98, 81, 25, 12, 48, 31]

Общий вид генератора следующий:

[выражение for переменная in последовательность]

В генераторах можно использовать логическое выражение с if. Например, в следующей программе создается список из целых чисел, в промежутке от 0 до 200, которые кратны 3 и 7.
Программа 9.7

L = [i for i in range(200) if not(i % 3) and not(i % 7)]
print(L)
[0, 21, 42, 63, 84, 105, 126, 147, 168, 189]

Генераторы не изменяют исходных списков, а создают новые:
Программа 9.8

A = list(range(1, 10))
B = [2 * it for it in A]
print(A)
print(B)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 4, 6, 8, 10, 12, 14, 16, 18]

Двумерные и многомерные списки

Список может быть многомерным, т. е. содержать в себе другие списки (иными словами – это массив массивов). Для простоты далее мы условимся, что вложенные списки будут иметь одинаковую размерность. Тогда для работы с многомерным списком необходимо использовать несколько индексов (для двумерного их будет 2). Список с одним индексом называют одномерными, с двумя — двумерными и т. д. Одномерный список нестрого соответствует вектору в математике, двумерный — матрице. Чаще всего применяются списки с одним или двумя индексами, реже — с тремя, ещё большее количество индексов встречается крайне редко.
Для того, чтобы создать многомерный список можно воспользоваться генератором:
Программа 9.9

L = [[0] * 3 for i in range(4)]
print(L)
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

В этой программе создается двумерный список с размерностью 4х3, заполненный нулями. Для того, чтобы вывести подобный список в компактной форме используется представление в виде матрицы – прямоугольной таблицы. Математическая матрица имеет вид:

L00 L01 L02 L03 L04 L05 ... L0j
L10 L11 L12 L13 L14 L15 ... L1j
L20 L21 L22 L23 L24 L25 ... L2j
L30 L31 L32 L33 L34 L35 ... L3j
L40 L41 L42 L43 L44 L45 ... L4j
L50 L51 L52 L53 L54 L55 ... L5j
... ... ... ... ... ... ...
Li0 Li1 Li2 Li3 Li4 Li5 ... Lij
  • Матрица, в которой число строк равно числу столбцов называется квадратной матрицей
  • Для обращения к элементу матрицы, необходимо использовать два индекса обозначающие номер строки и номер столбца. Например L[4][5]. В данном случае элемент списка L находится в пятой строке и шестом столбце.
  • Чтобы вывести матрицу используется структура вложенных циклов for и переменные с именами: i – для пересчета строк (вложенных списков) и j – для пересчета столбцов (элементов вложенных списков).
  • Если номер строки элемента совпадает с номером столбца (i = j), это означает, что элемент лежит на главной диагонали матрицы.
  • Если элемент лежит на побочной диагонали, то индексы связаны с числом элементов в строке или столбце (n) следующим равенством: i + j = n - 1.
  • Элементы находящиеся выше главной диагонали подчиняются правилу: i < j, а ниже i > j
  • Элементы находящиеся выше побочной диагонали подчиняются правилу i + j < n, а находящиеся ниже i + j => n
Двумерный список можно инициализировать явно. В следующей программе создается массив 3х3 и выводятся его элементы в виде матрицы. Для вывода используется разновидность цикла for с перебором самих элементов.
Программа 9.10

L = [[2,3,4],[4,3,7],[8,3,5]]
for row in L:
    for item in row:
        print(item, "", end="")
    print()
2 3 4
4 3 7
8 3 5

Рассмотрим задачу заполнения двумерного списка случайными числами. В программе ниже сначала генератором создаётся список содержащий 7 пустых списков. Во вложенном цикле с помощью метода append() эти списки будут заполнены равным количеством элементов (7).
Программа 9.11 Заполнить двумерный массив размера 7х7 случайными числами и вывести его элементы

from random import randint
L = [[] * 7 for i in range(7)]
for i in range(7):
    for j in range(7):
        L[i].append(randint(10,99))
        print("{:<3d}".format(L[i][j]), end="")
    print()
89 52 93 10 88 93 53 
37 73 18 39 76 13 57 
72 76 95 21 57 36 70 
26 81 59 79 60 48 61 
74 46 19 40 40 54 51 
76 43 79 67 99 34 35 
62 55 50 73 75 95 28

Данная задача может быть решена более компактно, если использовать вложенные генераторы.
Программа 9.12

from random import randint
L = [[randint(10,99) for j in range(7)] for i in range(7)]
for i in range(7):
    for j in range(7):
        print("{:3d}".format(L[i][j]), end="")
    print()

Операции "+" и "*" применяемые к спискам

К спискам можно применять операции "+" и "*". Опреация "+" (конкатенация) — объединяет два списка или добавляет элемент к концу списка (последнее равносильно операции "+=").
Операция "*" — повторение списка. С помощью этой операции можно заполнить список одним элементом. Однако нельзя умножать сами списка, т. е. операция A * B приведет к ошибке.
Программа 9.13

A = [5] * 5
print("А =", A)
B = [2] * 5
print("B =", B)
C = A + B
print("C =", C)
B += A
print("B =", B)
А = [5, 5, 5, 5, 5]
B = [2, 2, 2, 2, 2]
C = [5, 5, 5, 5, 5, 2, 2, 2, 2, 2]
B = [2, 2, 2, 2, 2, 5, 5, 5, 5, 5]

Срезы

Ранее мы уже показали, что доступ к элементу последовательности можно получить обратившись по индексу и имени, например: L[0] — первый элемент, L[1] — второй и т. д. Последний элемент последовательности в которой содержится N элементов будет иметь индекс равный N - 1. Однако в Python можно использовать и отрицательную индексацию. Пусть дан список L (изображенный на рисунке). Тогда к последнему элементу можно обратиться так: L[-1] (или L[8]), к предпоследнему L[-2] и т. д. В итоге, первый элемент — L[-9], он же L[0].

Такое представление индекса очень удобно, если необходимо проводить некоторые операции с конца последовательности, в особенности, когда размер этой последовательности весьма велик. Отрицательный индекс также удобно применять в срезах.
Срезы (slices) — операции над последовательностями в результате которых производится выборка определенных элементов массива с помощью индексов. Срезы можно получать для любой последовательности. Синтаксис срезов:

L[start:stop:step]

где:

  • start — индекс начала среза (индексы могут быть отрицательными),
  • stop — индекс конца среза ([start, stop)),
  • step — шаг (может быть отрицательным).
start, stop и step могут быть только целыми числами или целочисленными переменными.
Срезы позволяют выполнять довольно сложные алгоритмы, запись которых выглядит очень компактно. Обычно срезы используются для создания новых последовательностей. Срезы можно использовать в различных операциях, например, конкатенации. Однако, срезы нельзя применять для изменения неизменяемых последовательностей таких, как строка. Если осуществляется попытка получить срез несуществующих элементов, то будет возвращен пустой список. Приведем примеры использования срезов на примере списка L:
L = [4, 31, 7, 12, 0, 8, 10, 3, 11, 3, 28, 16, 5, 9]
Срез L[:] будет означать выбор всего списка, тогда копию списка L можно создать так:
B = L[:]
Последняя операция будет аналогична такой:
B = list(L)
Примечание. Не путайте с операцией B = L, которая создаст новую ссылку на всё тот же список L!
Индекс stop не включается в срез!

print(L[1:5])
[31, 7, 12, 0]

Реверс массива можно сделать с помощью отрицательного шага:

print(L[::-1])
[9, 5, 16, 28, 3, 11, 3, 10, 8, 0, 12, 7, 31, 4]

Если шаг отрицателен, то выборка элементов будет выполняться с конца массива, т. е. справа налево.
Индексы start или stop можно опустить это будет означать, что срез выполняется от начала или до конца, соответственно.

print(L[:8])
[4, 31, 7, 12, 0, 8, 10, 3]
print(L[8:])
[11, 3, 28, 16, 5, 9]
print(L[-8:])
[10, 3, 11, 3, 28, 16, 5, 9]
print(L[:-8])
[4, 31, 7, 12, 0, 8]

Выполнение срезов с определенным шагом:

print(L[::2])
[4, 7, 0, 10, 11, 28, 5]
print(L[4:10:3])
[0, 3]
print(L[-10::5])
[0, 3]
print(L[-3:-10:-3])
[16, 11, 8]

Изменение списков с помощью срезов

С помощью срезов можно выполнять операции изменения как исходного среза, так и любых других. Приведем примеры срезов с конкатенацией:

A = [4, 31, 7, 12, 0, 8, 10]
B = [3, 11, 3, 28, 16, 5, 9]
L = A[2::2] + B[:2:-2]
print(L)
[7, 0, 10, 9, 16]

В следующей программе все элементы списка A, начиная со второго, будут заменены элементами списка B:

A = [4, 31, 7, 12, 0, 8, 10]
B = [3, 11, 3, 28, 16, 5, 9]
A[2:] = B
print(A)
[4, 31, 3, 11, 3, 28, 16, 5, 9]

Примечание. Если целевому списку (получаемого в виде среза) присвоить значения списка имеющего больший размер, то будет сгенерировано исключение ValueError. Например:

A = [4, 31, 7, 12, 0, 8, 10]
B = [3, 11, 3, 28, 16, 5, 9]
A[::3] = B[:]
Traceback (most recent call last):
  File "srez2.py", line 3, in 
    A[::3] = B[:]
ValueError: attempt to assign sequence of size 7 to extended slice of size 3

С помощью операции del и срезов можно удалять в списках произвольные элементы.

A = [4, 31, 7, 12, 0, 8, 10]
B = [3, 11, 3, 28, 16, 5, 9]
del A[::2]
del B[-1:-5:-1]
print(A)
print(B)
[31, 12, 8]
[3, 11, 3]
Домашнее задание
  1. Дано целое число N (> 0). Сформировать и вывести целочисленный массив размера N, содержащий N первых положительных нечетных чисел: 1, 3, 5, … .
  2. Дан целочисленный массив размера N. Вывести все содержащиеся в данном массиве нечетные числа в порядке возрастания их индексов, а также их количество K.
  3. Дан целочисленный массив размера N. Вывести вначале все содержащиеся в данном массиве четные числа в порядке возрастания их индексов, а затем — все нечетные числа в порядке убывания их индексов.
Print Friendly, PDF & Email

Comments are closed.