ТЕОРИЯ И ПРАКТИКА

  • Ввод и вывод данных
    • Задачи
  • Условия
    • Задачи
  • Цикл for
    • Задачи
  • Строки
    • Задачи
  • Цикл while
    • Задачи
  • Списки
    • Задачи
  • Двумерные массивы
    • Задачи
  • Словари
    • Задачи
  • Множества
    • Задачи
  • Функции и рекурсия
    • Задачи

Занятие 7. Двумерные массивы в питоне

Двумерные массивы Python: структура, данные, типичные представления

Что такое двумерные массивы в Python

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

# Пример таблицы 2x3 (2 строки, 3 столбца)
table = [
    [1, 2, 3],
    [4, 5, 6]
]
                        

Двумерные массивы находят применение в разработке игр для создания игровых полей, в обработке изображений для работы с пикселями, а также при работе с любыми табличными данными.

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

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

# Каждая строка - отдельный список
shelf1 = [10, 20, 30]
shelf2 = [40, 50, 60]
box = [shelf1, shelf2]
print(box)  # [[10, 20, 30], [40, 50, 60]]
                        

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

Ключевые отличия двумерных массивов от одномерных

Одномерный массив или список представляет собой простую линейную последовательность элементов. Двумерный же массив имеет более сложную, табличную структуру.

# Одномерный - просто список
line = [1, 2, 3, 4, 5]

# Двумерный - список списков
cinema = [
    [1, 2, 3],  # первый ряд
    [4, 5, 6]   # второй ряд
]
                        

Одномерные списки удобны для простых последовательностей, таких как список покупок, в то время как двумерные массивы незаменимы для задач, требующих структурирования данных по двум осям, например, при планировании по времени и категориям.

Области применения двумерных массивов

Матрицы в математических вычислениях

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

matrix = [
    [1, 2],
    [3, 4]
]
                        

Представление изображений

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

# Простое чёрно-белое изображение 3x3
image = [
    [0, 1, 0],  # 0 = чёрный, 1 = белый
    [1, 1, 1],
    [0, 1, 0]
]
                        

Способы создания двумерных массивов в Python

Создание статического (заранее определенного) массива

Ручное объявление массива

Если все значения массива известны заранее, его можно объявить напрямую в коде.

menu = [
    ["каша", "суп", "котлета"],      # понедельник
    ["омлет", "борщ", "рыба"],       # вторник
    ["хлопья", "салат", "курица"]    # среда
]
                        
Пример: Игровое поле для крестиков-ноликов

Частый пример статического объявления — создание начального состояния игрового поля.

game_field = [
    [" ", " ", " "],
    [" ", " ", " "],
    [" ", " ", " "]
]
                        

Создание пустого или инициализированного массива

Использование генераторов списков

Для создания массивов заданного размера, заполненных начальными значениями (например, нулями или None), удобно использовать генераторы списков.

# Создаём пустую таблицу 3x4 (3 строки, 4 столбца), заполненную нулями
rows, cols = 3, 4
empty_table = [[0 for j in range(cols)] for i in range(rows)]
print(empty_table)  # [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
                        

Важность глубокого копирования

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

# НЕПРАВИЛЬНО: все строки ссылаются на один и тот же объект
bad_table = [[0] * 3] * 2
bad_table[0][0] = 5
print(bad_table)  # [[5, 0, 0], [5, 0, 0]] - изменились обе строки!

# ПРАВИЛЬНО: каждая строка является независимым объектом
good_table = [[0 for _ in range(3)] for _ in range(2)]
good_table[0][0] = 5
print(good_table)  # [[5, 0, 0], [0, 0, 0]] - изменилась только первая
                        

Заполнение массива одним значением

Генераторы списков также позволяют легко заполнить весь массив одним и тем же значением.

# Заполнить всю таблицу 2x3 числом 7
value = 7
rows, cols = 2, 3
table = [[value for j in range(cols)] for i in range(rows)]
print(table)  # [[7, 7, 7], [7, 7, 7]]
                        

Создание массива из строки с разделителями

Разделение по пробелам и символам новой строки

Часто данные для двумерного массива поступают в виде одной строки, где строки разделены символом \n, а элементы — пробелами.

text = "1 2 3\n4 5 6\n7 8 9"
lines = text.split('\n')  # разделяем на строки
table = []
for line in lines:
    row = line.split(' ')  # разделяем на элементы
    table.append(row)
print(table)  # [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
                        

Преобразование строковых элементов в числа

После разделения строки все элементы будут иметь строковый тип. Для математических операций их необходимо преобразовать в числа.

text = "1 2 3\n4 5 6"
lines = text.split('\n')
table = []
for line in lines:
    row = [int(x) for x in line.split(' ')]  # превращаем строки в числа
    table.append(row)
print(table)  # [[1, 2, 3], [4, 5, 6]]
                        

Этот метод часто используется при чтении данных из файлов или обработке пользовательского ввода.

Работа с элементами двумерного массива

Чтение и изменение элементов по индексам

Чтение значения по индексам [row][col]

Доступ к конкретному элементу осуществляется путем указания его индексов строки и столбца. Помните, что индексация в Python начинается с 0.

cinema = [
    ["свободно", "занято", "свободно"],
    ["занято", "свободно", "занято"]
]

# Проверяем место во втором ряду, третье место
# Индекс строки 1 (второй ряд), индекс столбца 2 (третье место)
seat = cinema[1][2]
print(seat)  # "занято"
                        

Изменение значения по индексам

Аналогичным образом можно изменить значение любого элемента массива.

game = [
    [".", ".", "."],
    [".", ".", "."],
    [".", ".", "."]
]

# Ставим крестик в центр поля (индексы 1, 1)
game[1][1] = "X"
print(game)
# Вывод:
# [['.', '.', '.'],
#  ['.', 'X', '.'],
#  ['.', '.', '.']]
                        

Доступ к строкам и столбцам

Обращение к целой строке

Чтобы получить всю строку целиком, достаточно указать только ее индекс.

grades = [
    [5, 4, 3],  # математика
    [4, 5, 5],  # русский язык
    [3, 4, 4]   # физика
]

math_grades = grades[0]  # все оценки по математике (первая строка)
print(math_grades)  # [5, 4, 3]
                        

Обращение к целому столбцу

Получение столбца требует итерации по всем строкам массива и извлечения элемента с нужным индексом столбца из каждой строки.

grades = [
    [5, 4, 3],  # оценки по 1, 2, 3 предмету
    [4, 5, 5],
    [3, 4, 4]
]

# Все оценки по второму предмету (индекс столбца 1)
second_subject_grades = [row[1] for row in grades]
print(second_subject_grades)  # [4, 5, 4]
                        

Безопасный доступ и обработка ошибок

Обработка исключения IndexError с помощью try-except

При попытке обратиться к несуществующему индексу возникнет ошибка IndexError. Ее можно перехватить с помощью блока try-except.

table = [[1, 2], [3, 4]]

try:
    value = table[5][0]  # такой строки не существует
    print(value)
except IndexError:
    print("Элемента с такими индексами в таблице нет!")
                        

Предварительная проверка границ массива

Более надежный способ — проверять индексы перед обращением к элементу. Это позволяет избежать исключений и управлять логикой программы.

def safe_get(table, row, col):
    # Проверяем, что индекс строки и столбца находятся в допустимых границах
    if 0 <= row < len(table) and 0 <= col < len(table[0]):
        return table[row][col]
    else:
        return None  # или любое другое значение по умолчанию

table = [[1, 2], [3, 4]]
print(safe_get(table, 1, 1))  # 4
print(safe_get(table, 5, 0))  # None
                        

Итерация и обход двумерных массивов

Простая итерация по строкам

Самый простой способ обхода — это перебор всех строк массива.

bookshelf = [
    ["Война и мир", "Анна Каренина"],
    ["Гарри Поттер", "Хоббит"],
    ["Шерлок Холмс", "Дракула"]
]

for shelf in bookshelf:
    print("На полке находятся книги:", shelf)
                        

Итерация по всем элементам через вложенные циклы

Для доступа к каждому отдельному элементу используются вложенные циклы: внешний цикл перебирает строки, а внутренний — элементы в текущей строке.

house = [
    ["диван", "стол", "стул"],
    ["кровать", "шкаф"],
    ["плита", "холодильник", "мойка"]
]

for room in house:
    for item in room:
        print("Найден предмет:", item)
                        

Доступ к индексам с помощью range()

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

storage = [
    ["молоток", "пила"],
    ["гвозди", "шурупы", "дюбели"],
    ["краска", "кисть"]
]

for i in range(len(storage)):
    for j in range(len(storage[i])):
        print(f"Полка {i}, ящик {j}: {storage[i][j]}")
                        

Использование функции enumerate() для получения индексов и значений

Более "питонический" и удобный способ получить и индексы, и значения — это использовать функцию enumerate().

data = [
    [10, 20],
    [30, 40, 50],
    [60]
]

for i, row in enumerate(data):
    for j, value in enumerate(row):
        print(f"Позиция ({i},{j}): {value}")
                        

Основные операции с двумерными массивами

Транспонирование матрицы

Транспонирование — это операция, при которой строки и столбцы матрицы меняются местами.

Транспонирование через list comprehension

Это компактный и эффективный способ выполнить транспонирование.

original = [
    [1, 2, 3],
    [4, 5, 6]
]

transposed = [[row[i] for row in original] for i in range(len(original[0]))]
for i in transposed:
    print(i)
                        

Транспонирование с помощью функции zip()

Функция zip() в сочетании с оператором распаковки * предоставляет еще более элегантный способ.

original = [
    [1, 2, 3],
    [4, 5, 6]
]

# zip() возвращает итератор кортежей
transposed_tuples = list(zip(*original))
print(transposed_tuples)  # [(1, 4), (2, 5), (3, 6)]

# Если на выходе нужны списки, а не кортежи
transposed_lists = [list(col) for col in zip(*original)]
print(transposed_lists) # [[1, 4], [2, 5], [3, 6]]
                        

Поиск максимального значения

Поиск максимума в каждой строке

Можно легко найти максимальный элемент в каждой строке, применив функцию max() к каждому вложенному списку.

heights = [
    [170, 180, 165],
    [175, 190, 160],
    [185, 155, 172]
]

for i, row in enumerate(heights):
    max_height = max(row)
    print(f"В ряду {i} максимальное значение: {max_height}")
                        

Поиск абсолютного максимума во всем массиве

Чтобы найти максимальный элемент во всем двумерном массиве, можно использовать вложенный генератор.

heights = [
    [170, 180, 165],
    [175, 190, 160],
    [185, 155, 172]
]

overall_max = max(max(row) for row in heights)
print(f"Максимальное значение во всем массиве: {overall_max}")
                        

Вычисление суммы всех элементов

Сумму всех элементов можно найти, просуммировав суммы элементов каждой строки.

wallets = [
    [100, 50, 200],
    [75, 300, 25],
    [150, 90]
]

total = sum(sum(row) for row in wallets)
print(f"Сумма всех элементов: {total}")
                        

Проверка элементов на соответствие условиям

Эти операции широко применяются в анализе данных, статистике и контроле качества.

Проверка на наличие определенного типа значений

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

Пример: Поиск отрицательных чисел
temperatures = [
    [5, -2, 10],
    [15, 8, -5],
    [20, 12, 18]
]

has_negative = any(temp < 0 for row in temperatures for temp in row)
print("В массиве есть отрицательные числа:", has_negative)  # True
                        

Подсчет количества элементов, удовлетворяющих условию

Функция sum() в сочетании с генераторным выражением позволяет подсчитать количество элементов, для которых условие истинно (так как True интерпретируется как 1, а False как 0).

Пример: Подсчет оценок
exam_results = [
    [5, 4, 3, 5],
    [4, 5, 5, 4],
    [3, 4, 5, 3]
]

fives_count = sum(grade == 5 for row in exam_results for grade in row)
print(f"Количество оценок '5': {fives_count}")
                        

Генерация двумерных массивов

Генерация матрицы на основе формулы

Генераторы списков позволяют создавать матрицы, значения элементов которых вычисляются по заданной формуле на основе их индексов.

# Таблица умножения 5x5
rows, cols = 5, 5
multiplication_table = [[(i + 1) * (j + 1) for j in range(cols)] for i in range(rows)]
# Выведем третью строку (умножение на 3)
print(multiplication_table[2])  # [3, 6, 9, 12, 15]
                        

Генерация матрицы с использованием условий

В генераторы можно встраивать условные операторы для создания сложных структур.

# Шахматная доска 8x8 (0 и 1)
chessboard = [[1 if (i + j) % 2 == 0 else 0 for j in range(8)] for i in range(8)]
                        

Часто используемые типы матриц

Нулевая матрица

Матрица, все элементы которой равны нулю.

# Матрица из нулей размером 4x4
zeros = [[0 for j in range(4)] for i in range(4)]
print(zeros)
                        

Единичная матрица

Квадратная матрица, у которой на главной диагонали стоят единицы, а все остальные элементы равны нулю.

# Единичная матрица 3x3
identity = [[1 if i == j else 0 for j in range(3)] for i in range(3)]
print(identity)  # [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
                        

Диагональная матрица

Квадратная матрица, у которой все элементы вне главной диагонали равны нулю.

# Матрица с заданными числами по диагонали
diagonal_values = [2, 4, 6]
size = len(diagonal_values)
diagonal_matrix = [[diagonal_values[i] if i == j else 0 for j in range(size)] for i in range(size)]
print(diagonal_matrix)  # [[2, 0, 0], [0, 4, 0], [0, 0, 6]]
                        

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

Преобразование в список кортежей

Иногда требуется преобразовать строки массива в неизменяемые кортежи.

table = [
    [1, 2, 3],
    [4, 5, 6]
]

tuple_list = [tuple(row) for row in table]
print(tuple_list)  # [(1, 2, 3), (4, 5, 6)]
                        

Получение множества уникальных элементов

Чтобы получить все уникальные значения из двумерного массива, его можно "развернуть" в одномерную последовательность и преобразовать в множество.

table = [
    [1, 2, 3],
    [2, 3, 4],
    [3, 4, 5]
]

unique_elements = set(element for row in table for element in row)
print(unique_elements)  # {1, 2, 3, 4, 5}
                        

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