Я пытаюсь принять участие в 30-дневном соревновании по программированию на Python, и в этой статье я буду играть в игру жизни.
Правила:
- Любая живая клетка, имеющая менее двух живых соседей, умирает, как будто из-за недостаточной численности населения.
- Любая живая клетка с двумя или тремя живыми соседями продолжает жить до следующего поколения.
- Любая живая клетка, имеющая более трех живых соседей, умирает, как будто от перенаселения.
- Любая мертвая клетка, имеющая ровно три живых соседа, становится живой клеткой, как бы путем размножения.
Реализацию Игры Жизни вы можете найти в предыдущей статье:
Проблема:
Решение (неоптимальное):
def game_of_life(board): def count_neighbors(x, y): count = 0 for i in range(-1, 2): for j in range(-1, 2): if i == 0 and j == 0: continue nx, ny = x + i, y + j if 0 <= nx < len(board) and 0 <= ny < len(board[0]) and board[nx][ny] in (1, -1): count += 1 return count for i in range(len(board)): for j in range(len(board[0])): neighbors = count_neighbors(i, j) if board[i][j] == 1 and (neighbors < 2 or neighbors > 3): board[i][j] = -1 elif board[i][j] == 0 and neighbors == 3: board[i][j] = 2 for i in range(len(board)): for j in range(len(board[0])): if board[i][j] == -1: board[i][j] = 0 elif board[i][j] == 2: board[i][j] = 1 ############################# OOP as well: ################################# class Solution(object): def __init__(self): self.board = [] def count_neighbors(self, x, y): count = 0 for i in range(-1, 2): for j in range(-1, 2): if i == 0 and j == 0: continue nx, ny = x + i, y + j if 0 <= nx < len(self.board) and 0 <= ny < len(self.board[0]) and self.board[nx][ny] in (1, -1): count += 1 return count def gameOfLife(self, board): self.board = board for i in range(len(self.board)): for j in range(len(self.board[0])): neighbors = self.count_neighbors(i, j) if self.board[i][j] == 1 and (neighbors < 2 or neighbors > 3): self.board[i][j] = -1 elif self.board[i][j] == 0 and neighbors == 3: self.board[i][j] = 2 for i in range(len(self.board)): for j in range(len(self.board[0])): if self.board[i][j] == -1: self.board[i][j] = 0 elif self.board[i][j] == 2: self.board[i][j] = 1
Объяснение:
- Мы определяем вспомогательную функцию
count_neighbors(x, y)
, которая принимает индексы строк и столбцов ячейки и возвращает количество живых соседей. - Мы перебираем строки и столбцы доски.
- Для каждой ячейки мы подсчитываем живых соседей с помощью функции
count_neighbors()
. - Мы применяем правила Игры Жизни, используя операторы
if
иelif
. Чтобы избежать изменения исходной доски, мы помечаем ячейки, которые должны стать мертвыми, цифрой -1, а ячейки, которые должны стать живыми, — цифрой 2. - Мы снова проходим по доске, чтобы обновить ячейки до их нового состояния.
Вывод:
Этот подход эффективен, поскольку ему нужно всего лишь дважды пройти по доске: один раз для расчета нового состояния на основе соседей, а затем еще раз для обновления ячеек.
Отмечая ячейки знаками -1 и 2 вместо прямого изменения их состояния, мы избегаем изменения состояния доски в процессе применения правил. Это помогает гарантировать, что правила применяются правильно и одновременно к каждой ячейке.
Решение (оптимальное):
class Solution(object): def __init__(self): self.board = [] def count_neighbors(self, x, y): directions = [(i, j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0] count = 0 for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < len(self.board) and 0 <= ny < len(self.board[0]) and self.board[nx][ny] in (1, -1): count += 1 return count def gameOfLife(self, board): self.board = board next_board = [[0] * len(self.board[0]) for _ in range(len(self.board))] for i in range(len(self.board)): for j in range(len(self.board[0])): neighbors = self.count_neighbors(i, j) if self.board[i][j] == 1 and (neighbors == 2 or neighbors == 3): next_board[i][j] = 1 elif self.board[i][j] == 0 and neighbors == 3: next_board[i][j] = 1 self.board[:] = next_board
Объяснение:
- Мы обновили метод
count_neighbors
, используя понимание списка для создания переменнойdirections
. Это упрощает код и делает его более читабельным. - Вместо того, чтобы дважды перебирать плату, мы создаем новую
next_board
с теми же размерами, что и исходная плата. Затем мы заполняем новое состояние непосредственно на этой новой доске на основе исходной доски. Это устраняет необходимость отмечать ячейки значениями -1 и 2 и выполнять двойную итерацию. - После расчета следующего состояния мы обновляем исходную плату на месте, используя назначение среза
self.board[:] = next_board
.
Заключение:
Оптимизированный код становится более кратким, а одиночный цикл сокращает количество итераций. Однако за это приходится платить увеличением использования памяти из-за создания файла next_board
. Это компромисс между сложностью времени и сложностью пространства.
Лучшее решение:
class Solution(object): def __init__(self): self.board = [] def gameOfLifeInfinite(self, live): ctr = collections.Counter((I, J) for i, j in live for I in range(i-1, i+2) for J in range(j-1, j+2) if I != i or J != j) return {ij for ij in ctr if ctr[ij] == 3 or ctr[ij] == 2 and ij in live} def gameOfLife(self, board): live = {(i, j) for i, row in enumerate(board) for j, live in enumerate(row) if live} live = self.gameOfLifeInfinite(live) for i, row in enumerate(board): for j in range(len(row)): row[j] = int((i, j) in live)
Дополнительные ресурсы:
Как игра жизни использовалась для создания искусства, довольно интересно!
Игра жизни и раннее генеративное искусство
от Theo Kingdom Игра жизни — это игра с нулевым участием игроков, что означает, что во время игры никто из игроков не участвует. есть мнение…sweetpeagallery.ca»
Спасибо за чтение и за вашу поддержку.
Если что-то неясно или у вас возникли трудности, свяжитесь с нами через Medium или LinkedIn.