EduBrick
классная работа

Двумерные списки и таблицы

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Читать таблицу и обращаться к клетке по двум номерам
  • Обходить таблицу по строкам, по столбцам и по диагоналям
  • Работать с соседями клетки, не выходя за края
  • Создавать таблицу так, чтобы строки не оказались одной и той же

Как устроено занятие

Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.

Дальше 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] — это ii-я строка, jj-й столбец. Перепутать легко, а на квадратной таблице ошибка может долго не проявляться — программа будет работать, но с транспонированными данными.

Количество строк — len(table), количество столбцов — len(table[0]). Второе честно только для прямоугольных таблиц, но других у нас и не будет.

теория

Как прочитать таблицу

Строки читаются по одной, каждая — знакомым выражением из занятия 18:

n, m = map(int, input().split())

table = []
for _ in range(n):
    table.append(list(map(int, input().split())))

Пустой список, потом nn раз добавили в него по строке. Метод append дописывает элемент в конец — им же вы собирали ответы в прошлых занятиях.

Как создать таблицу нужного размера

Иногда таблицу надо не прочитать, а построить — например, заполнить нулями. И вот здесь ждёт ловушка:

table = [[0] * m] * n        # ВЫГЛЯДИТ правильно
table[0][0] = 5
print(table)                 # изменился весь столбец!

Умножение списка копирует не содержимое, а ссылку. Получается nn имён для одной и той же строки — вспомните 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]

Диагонали — единственное место, где хватает одного цикла: там номер столбца однозначно определяется номером строки.

теория

Соседи клетки и края

У клетки (i,j)(i, j) четыре соседа по стороне: сверху, снизу, слева, справа.

сосед координаты
сверху 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: они ломают всё, где предполагается «внутри»;
  • прямоугольная, а не квадратная — если стороны разной длины, перепутанные индексы сразу проявятся;
  • все числа одинаковые — проверяет строгие сравнения в задачах про максимумы.
теория

Практика: пятнадцать задач

Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.

Таблицы небольшие — до трёхсот на триста, то есть до девяноста тысяч клеток. Полный обход занимает мгновение, поэтому почти везде можно не хитрить: пройти всё и посчитать.

Проверяйте решения на узких таблицах — в одну строку и в один столбец. Именно на них ломается большинство ошибок с краями и соседями.

лестница задач
развёрнутый ответ

Таблица, у которой все строки одинаковые

Задача: «постройте таблицу из nn строк и mm столбцов, в клетке (i,j)(i, j) должно стоять произведение номеров iji \cdot j; номера считаются с единицы».

Ученик написал:

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)

При n=3n = 3 и m=3m = 3 программа печатает три одинаковые строки 3 6 9. Объясните, что произошло, почему в каждой строке оказались именно эти числа, и как это исправить.

Войдите, чтобы ответить.