Чему научитесь
- Читать строку чисел в список одним выражением
- Обращаться к элементам по номеру и пользоваться срезами
- Считать сумму, минимум и максимум готовыми функциями
- Отличать копию списка от второго имени того же списка
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Когда числа нужны все сразу
В занятии про накопление был важный приём: читать числа по одному и сразу обрабатывать. Он экономит память и решает половину задач.
Но не все. Попробуйте так вывести числа в обратном порядке — не выйдет: чтобы напечатать последнее первым, надо дождаться конца, а к тому времени начало уже забыто.
Для таких задач нужен список — пронумерованный набор значений, который живёт целиком.
a = [3, 8, 1, 9]
print(len(a)) # 4 — сколько элементов
print(a[0]) # 3 — первый
print(a[3]) # 9 — четвёртый
print(a[-1]) # 9 — последний
Всё это вы уже видели у строк: нумерация с нуля, квадратные скобки, отрицательные номера, len. Список устроен так же — разница в том, что элементы не символы, а любые значения, и список можно менять.
a[0] = 100 # у строки так было нельзя, у списка можно
Как прочитать список
Числа в олимпиадных задачах обычно идут одной строкой через пробел. Читается такая строка одним выражением:
a = list(map(int, input().split()))
Выражение выглядит громоздко, но разбирается по шагам справа налево:
| шаг | что получается |
|---|---|
input() |
строка "3 8 1 9" |
.split() |
список строк ["3", "8", "1", "9"] |
map(int, …) |
те же значения, но числами |
list(…) |
список чисел [3, 8, 1, 9] |
map применяет функцию к каждому элементу — здесь превращает каждую строку в число. Без list результат тоже работает в цикле, но списком ещё не является, поэтому его оборачивают.
Если в первой строке дано количество, его читают отдельно:
n = int(input())
a = list(map(int, input().split()))
Заметьте, что n после этого почти не нужен: длину списка всегда можно спросить у него самого через len(a). Полагаться лучше на len — если в тесте окажется лишний пробел или число, расхождение выявится сразу.
Проверка: что читает эта строка
a = input().split()
print(a[0] + a[1])
На вход подали 3 8. Что выведет программа?
Готовые действия со списком
Сумму, минимум и максимум вы писали циклом в занятии 8. Для списка всё это уже есть:
print(sum(a)) # сумма
print(min(a)) # наименьший
print(max(a)) # наибольший
print(len(a)) # количество
Пользуйтесь ими: короче, быстрее и без риска ошибиться в начальном значении накопителя.
Срезы работают так же, как у строк:
print(a[:3]) # первые три элемента
print(a[-2:]) # последние два
print(a[::-1]) # список в обратном порядке
print(sum(a[:k])) # сумма первых k
Как вывести список
Печать print(a) выдаст [3, 8, 1, 9] — со скобками и запятыми. Проверяющей системе нужны числа через пробел, поэтому пишут иначе:
print(*a) # 3 8 1 9
print(" ".join(map(str, a))) # то же самое
Звёздочка перед списком означает «передай элементы по отдельности». Это самый короткий способ, и в задачах используется именно он.
Ссылка против копии
А теперь то, чего не было у строк и что удивляет всех.
a = [1, 2, 3]
b = a
b[0] = 100
print(a) # [100, 2, 3] — изменился и a!
Присваивание b = a не создаёт второй список. Оно даёт второе имя тому же самому списку. Меняя b, вы меняете a — это один объект, у которого две этикетки.
Со строками такого не бывало просто потому, что строку вообще нельзя изменить: любое действие создавало новую. Список изменяемый, и разница становится видна.
Как сделать настоящую копию
b = a[:] # срез создаёт новый список
b = list(a) # то же самое
Теперь b — отдельный список, и его изменения на a не влияют.
| запись | что делает |
|---|---|
b = a |
второе имя того же списка |
b = a[:] |
новый список с теми же значениями |
b = a[::-1] |
новый список, перевёрнутый |
a.reverse() |
переворачивает сам a, ничего не возвращая |
Последняя строка — частая ловушка: b = a.reverse() положит в b пустоту, а a окажется перевёрнутым. Если исходный порядок ещё нужен, берите срез.
Проверка: сколько элементов
Список a содержит 10 элементов. Сколько элементов в срезе a[2:7]?
Введите целое число.
Три ошибки этого занятия
Забытый int. a = input().split() даёт список строк. Сложение таких «чисел» их склеит, а сравнение будет по алфавиту: "10" < "9" истинно. Признак: ответ выглядит бессмысленно, но программа не падает.
Выход за границу. Последний допустимый номер — len(a) - 1. Обращение к a[len(a)] роняет программу с list index out of range. В отличие от среза, обращение по номеру не прощает.
Список испорчен по дороге. Отсортировали, перевернули на месте или изменили элементы — а исходный порядок ещё был нужен. Признак: первая часть ответа верна, вторая нет.
Как проверять себя
- список из одного элемента — первый и последний совпадают, соседних пар нет вовсе;
- все элементы одинаковые — проверяет строгие сравнения;
- отрицательные значения — ловят накопитель, начатый с нуля;
- вывод после изменения — если список нужен дважды, не испортили ли его в первый раз.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Большая часть решается в две-три строки: прочитать, применить готовую функцию или срез, вывести через print(*a). Это нормально — смысл занятия в том, чтобы освоить чтение и индексацию, а не в сложных алгоритмах.
В нескольких задачах список нужно вывести дважды — до и после изменения. Там и проверяется, поняли ли вы разницу между ссылкой и копией.
Список, который изменился сам
Задача: «дан список чисел; выведите его в обратном порядке, а затем в исходном».
Ученик написал:
a = list(map(int, input().split()))
b = a
b.reverse()
print(*b)
print(*a)
На входе 1 2 3 программа выводит две одинаковые строки 3 2 1, хотя вторая должна быть 1 2 3. Объясните, почему так вышло, и приведите два способа исправить.