Чему научитесь
- Читать таблицу и обращаться к клетке по двум номерам
- Обходить таблицу по строкам, по столбцам и по диагоналям
- Работать с соседями клетки, не выходя за края
- Создавать таблицу так, чтобы строки не оказались одной и той же
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Список, элементы которого — списки
Расписание, игровое поле, таблица результатов — данные, у которых две координаты: строка и столбец. Для них берут список списков.
table = [
[3, 8, 1],
[9, 5, 2],
]
print(table[0]) # [3, 8, 1] — первая строка целиком
print(table[0][2]) # 1 — третье число первой строки
print(len(table)) # 2 — сколько строк
print(len(table[0])) # 3 — сколько столбцов
Никакого нового устройства здесь нет: table — обычный список, просто каждый его элемент сам список. Отсюда и двойные скобки: первая выбирает строку, вторая — число в ней.
Порядок индексов всегда один: сначала строка, потом столбец. table[i][j] — это -я строка, -й столбец. Перепутать легко, а на квадратной таблице ошибка может долго не проявляться — программа будет работать, но с транспонированными данными.
Количество строк — len(table), количество столбцов — len(table[0]). Второе честно только для прямоугольных таблиц, но других у нас и не будет.
Как прочитать таблицу
Строки читаются по одной, каждая — знакомым выражением из занятия 18:
n, m = map(int, input().split())
table = []
for _ in range(n):
table.append(list(map(int, input().split())))
Пустой список, потом раз добавили в него по строке. Метод append дописывает элемент в конец — им же вы собирали ответы в прошлых занятиях.
Как создать таблицу нужного размера
Иногда таблицу надо не прочитать, а построить — например, заполнить нулями. И вот здесь ждёт ловушка:
table = [[0] * m] * n # ВЫГЛЯДИТ правильно
table[0][0] = 5
print(table) # изменился весь столбец!
Умножение списка копирует не содержимое, а ссылку. Получается имён для одной и той же строки — вспомните b = a из занятия про списки. Запись в одну клетку меняет их все.
Правильно — создавать каждую строку отдельно:
table = []
for _ in range(n):
table.append([0] * m) # новый список на каждую строку
Здесь [0] * m безопасно: числа неизменяемы, копировать нечего.
Проверка: что напечатается
table = [[0] * 3] * 2
table[0][0] = 5
print(table)
Обход по строкам и по столбцам
По строкам — привычно и коротко:
for row in table:
print(sum(row)) # каждая строка — обычный список
А вот столбца готовым списком не существует: числа одного столбца лежат в разных строках. Его собирают вложенным циклом, где внешний идёт по столбцам:
for j in range(m):
total = 0
for i in range(n):
total += table[i][j] # обратите внимание: i внутри
print(total)
Разница между обходами — только в том, какой цикл снаружи. Это тот же вложенный цикл из занятия 11, просто теперь у него есть смысл: он ходит по клеткам.
| что нужно | как обойти |
|---|---|
| все клетки | for i in range(n): for j in range(m): |
| каждая строка целиком | for row in table: |
| каждый столбец целиком | внешний цикл по j, внутренний по i |
| главная диагональ | for i in range(n): table[i][i] |
| побочная диагональ | for i in range(n): table[i][n - 1 - i] |
Диагонали — единственное место, где хватает одного цикла: там номер столбца однозначно определяется номером строки.
Соседи клетки и края
У клетки четыре соседа по стороне: сверху, снизу, слева, справа.
| сосед | координаты |
|---|---|
| сверху | table[i - 1][j] |
| снизу | table[i + 1][j] |
| слева | table[i][j - 1] |
| справа | table[i][j + 1] |
Беда в том, что у клеток с краю соседей меньше. Обращение к table[-1][j] не упадёт — Python поймёт минус единицу как «последняя строка» и молча возьмёт число с другого конца таблицы. Ошибка тихая и потому опасная.
Поэтому каждого соседа проверяют на существование:
total = 0
if i > 0: total += table[i - 1][j]
if i < n - 1: total += table[i + 1][j]
if j > 0: total += table[i][j - 1]
if j < m - 1: total += table[i][j + 1]
Второй способ — сузить границы цикла так, чтобы крайние клетки в него вообще не попали: range(1, n - 1). Он короче, но годится только когда крайние клетки по условию и не нужны.
Клетки по краю описываются одним условием: i == 0 or j == 0 or i == n - 1 or j == m - 1. Складывать четыре стороны по отдельности — обычная ошибка: углы попадут в сумму дважды.
Проверка: сколько клеток внутри
В таблице 5 строк и 7 столбцов.
Сколько в ней клеток, не стоящих в крайних строках и крайних столбцах? Введите целое число.
Три ошибки этого занятия
[[0] * m] * n. Все строки — одна и та же. Признак: запись в клетку меняет целый столбец. Разобрано выше.
Индексы переставлены. table[j][i] вместо table[i][j]. На прямоугольной таблице программа упадёт с list index out of range, на квадратной — молча посчитает не то.
Сосед за краем. table[i - 1][j] при i = 0 берёт последнюю строку вместо несуществующей. Ошибки нет, ответ неверный. Проверяйте существование соседа, а не надейтесь на падение.
Как проверять себя
- таблица один на один — у клетки нет ни одного соседа, диагонали совпадают;
- одна строка и один столбец — то есть
1 × mиn × 1: они ломают всё, где предполагается «внутри»; - прямоугольная, а не квадратная — если стороны разной длины, перепутанные индексы сразу проявятся;
- все числа одинаковые — проверяет строгие сравнения в задачах про максимумы.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Таблицы небольшие — до трёхсот на триста, то есть до девяноста тысяч клеток. Полный обход занимает мгновение, поэтому почти везде можно не хитрить: пройти всё и посчитать.
Проверяйте решения на узких таблицах — в одну строку и в один столбец. Именно на них ломается большинство ошибок с краями и соседями.
Таблица, у которой все строки одинаковые
Задача: «постройте таблицу из строк и столбцов, в клетке должно стоять произведение номеров ; номера считаются с единицы».
Ученик написал:
n, m = map(int, input().split())
table = [[0] * m] * n
for i in range(n):
for j in range(m):
table[i][j] = (i + 1) * (j + 1)
for row in table:
print(*row)
При и программа печатает три одинаковые строки 3 6 9. Объясните, что произошло, почему в каждой строке оказались именно эти числа, и как это исправить.