31. Двумерные списки (массивы, матрицы) в Python

Прежде, чем мы сделаем игру «Сапер» и «Блоки» нужно научится работать с двумерными списками.
Если просто, то двумерный список — это таблица. У каждой ячейки таблицы есть номер строки и номер столбца.
Т.е. каждая ячейка имеет не один номер, как в обычном списке, а два: номер строки и номер столбца.
Поэтому такие списки и называют «двумерными». В математике такие структуры называют «матрицы».
Создадим простой двумерный список:

a = [[1,2,3],[4,5,6]]
print(a)

Не очень похоже на таблицу, правда?
А если так:

a = [[1,2,3],[4,5,6]]
print(a[0])
print(a[1])

a[0] — это вся первая строка. Т.е. по сути, это элемент списка, который является списком.
len(a) — количество строк
len(a[0]) — количество элементов (столбцов) в первой строке.

Разумеется, что мы не будем заполнять и выводить это вручную.
Сначала посмотрим на вывод.
Взять все строки по очереди и вывести на экран.

for r in a:
    print(r)

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

А теперь посмотрим на заполнение:

a = []
k = 10 # просто начальное значение, может быть любым
for r in range(6): # 6 строк
    a.append([]) # создаем пустую строку
    for c in range(10): # в каждой строке - 10 элементов
        a[r].append(k) # добавляем очередной элемент в строку
        k += 1 # увеличиваем значение счетчика

for r in a:
    print(r)

Начал с k = 10 для того, чтобы все числа были двузначные (попробуйте начать с k = 1 и поймете, о чем я говорю)

Теперь — заполнение случайными значениями:

a = []
for r in range(6): # 6 строк
    a.append([]) # создаем пустую строку
    for c in range(10): # в каждой строке - 10 элементов
        a[r].append(rnd(10,100)) # добавляем очередной элемент в строку

А теперь создадим очень простой класс и двумерный список, который заполним экземплярами этого класса:

class cell():
    def __init__(self):
        self.n = rnd(10) 

a = []
nr = 6 # количество строк
nc = 11 # количество столбцов
for r in range(nr):
    a.append([])
    for c in range(nc):
        a[r].append(cell()) # добавляем очередной элемент в строку

for r in range(nr): # перебираем все строки по номерам
    for c in range(nc): # в каждой строке перебираем все столбцы по номерам
        print(a[r].n, end = ' ')
		# выводим на экран значения свойства "а" элемента, стоящего в данной строке и данном столбце
		# после вывода не нужно переходить на другую строку (на экране) - ставим пробел
    print() # когда выведем очередную строку, то переводим курсор (на экране) на новую строку

Усложним задачу. Сделаем класс «Ячейка» и нарисуем таблицу ячеек. Вначале класс и тестовая ячейка:

from random import randrange as rnd, choice
from tkinter import *
 
root = Tk()
root.geometry('800x600')
 
canv = Canvas(root, bg = 'white')
canv.pack(fill = BOTH, expand = 1)


m = 34 # размер ячеек
d = 2 # размер поля вокруг ячейки
nr = 6 # количество строк
nc = 8 # количество столбцов
x0 = m // 2 # отступ от левого края
y0 = m // 2 # отступ от вернего края
colors = ['red','yellow','cyan','green']
 
class cell():
    def __init__(self, r, c): # при создании указываем номер строки и столбца, в который помещаем
        self.n = rnd(10) # значение, с которым будем работать
        self.r = r # Номер сторки в двумерном списке.
        self.c = c # Номер столбца ...
        self.color = choice(colors) # случайный цвет из списка
        self.id = canv.create_rectangle(-100,0,-100,0,fill = self.color) # квадратик ячейки
        self.id_text = canv.create_text(-100,0, text = self.n, font = "Arial " + str(m//2)) # текст в квадратике, размер зависит от масштаба
        self.paint()
 
    def paint(self):
        x1 = x0 + self.c * m + d
        # рассчитать координаты левого верхнего угла.
        y1 = y0 + self.r * m + d
        # координаты правого нижнего угла.
        x2 = x1 + m - 2*d # - r
        y2 = y1 + m - 2*d
        canv.coords(self.id,x1,y1,x2,y2)
        canv.itemconfig(self.id,fill = self.color)
        # текст в центр ячейки
        x = (x1 + x2) / 2
        y = (y1 + y2) / 2
        canv.coords(self.id_text,x,y)
        canv.itemconfig(self.id_text, text = self.n)
 
c_test = cell(2,2)

mainloop()

В результате — грустная и одинокая «клетка» на экране. Случайного цвета и со случайным значением.

a = []
for r in range(nr): # 6 строк
    a.append([]) # создаем пустую строку
    for c in range(nc): # в каждой строке - 10 элементов
        a[r].append(cell(r,c)) # добавляем очередной элемент в строку

mainloop()

Мы использовали класс cell, потому что это удобно — «завернуть» несколько параметров в «одну переменную». Без использования класса мы смогли бы хранить только числовое значение в ячейке. А так у нас есть уже, как минимум, два значения — это цвет и числовое значение.

Рассмотрим перебор всех ячеек. Например, приведем в соответствие цвет и значение в ячейке: выделим значения от 0 до 2 голубым, от 3 до 5 зеленым, 6 — 8 — желтым и 9 — красным:

def task(event):
    for r in range(nr): # перебираем все строки
        for c in range(nc): # перебираем все столбцы
            if 0 <= a[r].n <= 2:
                a[r].color = 'cyan'
            elif a[r].n <= 5:
                a[r].color = 'green'
            elif a[r].n <= 8:
                a[r].color = 'yellow'
            else:
                a[r].color = 'red'

canv.bind('<Button-1>',task)

mainloop()

Не работает?
У меня тоже не работает.
Как выяснить, в чем может быть дело?
Вначале стоит проверить, выполняется ли функция task вообще. В этом поможет обычный print(‘!’)
Если выполняется, то так же нужно проверить выполнение какого-либо условия.
Все работает… но изменений-то не видно!
Ключевое слово видно.
Если еще не догадались, то подсказываю: нужно отобразить изменения.
a[r].paint()

Рассмотрим решение некоторых задач.

Найдем сумму всех:

def task(event):
    s = 0
    for r in range(nr):
        for c in range(nc):
            s += a[r].n
    print(s)

2014-10-22 12-17-35 Скриншот экрана

А теперь выведем результат не в консоль, а прямо на холст:

def task(event):
    s = 0
    for r in range(nr):
        for c in range(nc):
            s += a[r].n
    x1 = x0 + nc * m + d # координаты левого верхнего угла правой ячейки
    x1 = x1 + 2*m # отступим от нее на размер двух ячеек
    y1 = y0
    x2 = x1 + m
    y2 = y1 + m
    canv.create_rectangle(x1,y1,x2,y2)
    x = (x2 + x1) / 2
    y = (y2 + y1) / 2
    canv.create_text(x,y, text = s, font = 'Arial' + str(m//2))

2014-10-22 12-24-29 Скриншот экрана


Заполнение значениями по порядку, по строкам:

def task(event):
    k = 0
    for r in range(nr):
        for c in range(nc):
            a[r].n = k
            k += 1
            a[r].color = '#99ccdd'
            a[r].paint()
1. Заполнить одним цветом, значениями по порядку, по строкам
2014-10-22 12-30-20 Скриншот экрана




2. Заполнить одним цветом, значениями по порядку, по столбцам
2014-10-22 13-35-28 Скриншот экрана




3. Заполнить номерами строк
2014-10-22 12-32-12 Скриншот экрана




4. Заполнить номерами столбцов
2014-10-22 12-31-45 Скриншот экрана




5. Создать таблицу сложения
2014-10-22 12-42-40 Скриншот экрана




6. Создать таблицу умножения




7. Заполнить повторяющимся набором (1,2,3,4,5) по строкам
2014-10-22 12-44-03 Скриншот экрана




8*. Заполнить повторяющимся набором (1,1,2,2,3,3,4,4,5,5). Возможно, потребуется модуль itertools




9. Заполнить двумя цветами в шахматном порядке, без чисел
2014-10-22 12-46-31 Скриншот экрана




Заполнить случайными цветами и числами, затем:
10. Найти сумму всех элементов
11. Найти сумму красных всего массива
12*. Найти среднее для каждого цвета, вывести рядом
13**. Нарисовать столбчатую диаграмму по суммам каждого цвета. Возможно, потребуется matplotlib
14**. Нарисовать круговую диаграмму по суммам каждого цвета
15. Выделить нули оранжевым, остальные — серым
16. Строки, в которых нет нулей сделать оранжевыми2014-10-22 13-19-47 Скриншот экрана




17. Столбцы, в которых нет нулей сделать оранжевыми






Рассмотрим, как определить ячейку, по которой щелкнули мышкой.

Мы уже рассчитывали координаты по номеру строки и столбца. Надо просто сделать обратный расчет:

def task(event):
    c = (event.x - x0) // m
    r = (event.y - y0) // m
    a[r].color = 'orange'
    a[r].paint()
По щелчку:
18. Выделить оранжевым цветом элемент, по которому щелкнули, все остальные перекрасить в серый
19. Выделить оранжевым цветом все ячейки в текущем столбце
20. Выделить оранжевым цветом все ячейки в текущей строке
22. Выделить оранжевым цветом диагонали, на которых находится ячейка, все остальные отметить серым
23. Выделить оранжевым цветом соседей (слева, справа, сверху, снизу), остальные ячейки закрасить серым
24. Выделить ячейку и ее соседей по сторонам и диагоналям (все 8 ячеек)
25. Выделить ячейку, ее соседей и соседей соседей (4 + 8) Считать соседями только клетки, с которыми есть общая сторона
26. Записать 0 в ячейки, которые имеют такой же цвет, как и та, в которую щелкнули
27*. Значение ячейки, по которой щелкнули, увеличить на 50%, ее соседей на 25%, их соседей — на 10%
28. Если значение ячейки, по которой щелкнули, меньше всех своих соседей (по сторонам), то перекрасить ее в синий, если больше — то в красный. Иначе оставить, как есть.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *