Rešitev

Najprej preberemo podatke. Tu ni nič takšnega, česar ne bi že videli.

import numpy as np

mapping, state = open("example.txt").read().split("\n\n")
mapping = np.array([c == "#" for c in mapping.replace("\n", "")])
state = np.array([[c == "#" for c in v.strip()] for v in state.splitlines()])

Zdaj je mapping tabela, ki pove, v kakšno stanje se preslika posamezna vsota. Če bomo imeli vsote

sums = np.array([[7, 3, 2],
                 [0, 5, 1]])

se bo to preslikalo v

mapping[sums]
array([[ True, False,  True],
       [False, False, False]])

V zanki bomo počeli tole.

Najprej pripravimo tabelico, ki v sredi vsebuje trenutno stanje, okrog nje pa je dva elementa širok rob, ki vsebuje ničle za sode in enice z lihe korake, saj "neskončna okolica" utripa.

i = 0

h, w = state.shape
padded = np.full((h + 4, w + 4), i % 2)
padded[2:-2, 2:-2] = state

Matriko smo sestavili z np.full, potem pa v sredino prepisali trenutno stanje.

Nato pripravimo tabelico za vsote. Ta bo za dve polji večja od trenutnega stanja, saj se "obljudeni" del tabele vsakič poveča za 1 v vsako smer.

sums = np.zeros((h + 2, w + 2), dtype=int)

In zdaj je na vrsti seštevanje. Namesto da bi računali vsoto za vsako celico posebej, bomo prištevali padded k vsotam, takole:

p = 256
for y in range(3):
    for x in range(3):
        sums += p * padded[y:y + h + 2, x:x + w + 2]
        p //= 2

y in x sta odmik od zgornjega in od desnega roba, oba gresta od 0 do 1. padded[y:y + h + 2, x:x + w + 2] je torej "okno" v padded, ki je enakih dimenzij kot sums in se pomika prek padded. Prištevamo ga k sums (kar smemo, saj sta enakih dimenzij. Ob prvem prištevanju (tistem brez odmikov) ga množimo z 256, ob drugem k 128 in tako naprej.

Kako to deluje - in zakaj deluje - se prepričajte sami. Predstavljajte si nek element sum (ne ravno čisto robnega, naj bo nekje na sredi) in razmislite, kateri elementi padded se prištejejo vanj ter s kakšnimi faktorji.

Ko dobimo vsote, jih premapiramo čez mapping.

mapping[sums]
array([[False,  True,  True, False,  True,  True, False],
       [ True, False, False,  True, False,  True, False],
       [ True,  True, False,  True, False, False,  True],
       [ True,  True,  True,  True, False, False,  True],
       [False,  True, False, False,  True,  True, False],
       [False, False,  True,  True, False, False,  True],
       [False, False, False,  True, False,  True, False]])

To je to. Vse skupaj samo še zložimo v zanko.

import numpy as np

mapping, state = open("example.txt").read().split("\n\n")
mapping = np.array([c == "#" for c in mapping.replace("\n", "")])
state = np.array([[c == "#" for c in v.strip()] for v in state.splitlines()])

for i in range(50):
    h, w = state.shape
    padded = np.full((h + 4, w + 4), i % 2)
    padded[2:-2, 2:-2] = state
    sums = np.zeros((h + 2, w + 2), dtype=int)
    p = 256
    for y in range(3):
        for x in range(3):
            sums += p * padded[y:h + 2 + y, x:w + 2 + x]
            p //= 2
    state = mapping[sums]
print(np.sum(state))
5097

Kot sem rekel: nič posebnega, samo kup drobnih zafrkancij.

Temu, kar počnemo v tej nalogi, se sicer reče konvolucija. Uporabljamo jo v procesiranju signalov in obdelavi slik, tudi globoke nevronske mreže pogosto vsebujejo konvolucijski nivo, ki počne nekaj podobnega, kot delamo v tej nalogi. Zato ima modul scipy, ki ga pogosto uporabljamo skupaj z numpyjem, funkcijo scipy.signal.convolve2d, ki počne natančno to, kar delamo znotraj te zanke.