Dzisiejsze zadanie polegało na znalezieniu węzłów fali (antinodes) generowanych przez anteny o różnych częstotliwościach. Brzmi to skomplikowanie ale w praktyce zadanie nie było trudne. Rozwiązałem je w miarę szybko w Pythonie.

Treść

W mieście (dwuwymiarowa plansza), zanjdują się anteny o różnych częstotliwościach, oznacznanych przez różne znaki alfanumeryczne. Węzły fali pojawiają się w punktach które są w lini prostej na której leżą dwie antenty o tej samej częstotliwości ale tak że jedna z nich jest dwa razy dalej od drugiej.

Poniżej przedstawiony jest przykład w którym mamy dwie anteny o częstotliwości a, które tworzą dwa węzły fali (oznaczone jako #).

.#....
......
..a...
......
...a..
......
....#.

Rozwiązanie części 1

Parsowanie

Parsowanie wejścia nie było trudne, iterujemy po każdej lini i każdym znaku, jeśli jest on różny od . znaczy że znaleźliśmy antenę, chcę zagregować anteny o tych samych częstotliwościach, żeby móc analizować rózne częstotliwości niezależnie.

import fileinput
from collections import defaultdict

antenas = defaultdict(list)

for y, line in enumerate(fileinput.input()):
    for x, c in enumerate(line.strip()):
        if c != ".":
            antenas[c].append(complex(x, y))

Jak można zauważyć używam wbudowanego typu complex, czyli liczby zespolonej, nie ma co się bać, jedyne właściwości liczb zespolonych z których będziemy korzystać to to że składają się one z pary liczb i że dodając dwie liczby zespolone wynik uzysujemy dodając pierwszy komponent do pierwszego i drugi do drugiego. Wybór by użyć complex, został podjęty czysto ze względu na skrócenie kodu.

Znajdywanie węzłów

W zadaniu musimy policzyć ile unikalnych węzłów znajduje się na planszy, dlatego do trzymania znalezionych dotychczas węzłów użyjemy obiektu set, który nie trzyma duplikatów.

Użyjemy wbudowaniej funkcji itertools.combinations do wygenerowania wszystkich kombinacji, składających się z dwóch anten tych samych częstotliwości.

antinodes = set()

for frequency, positions in antenas.items():
    for a, b in itertools.combinations(positions, 2):
        diff = a - b
        antinodes.add(a + diff)
        antinodes.add(b - diff)

Filtrowanie i sumowanie węzłów

Okazuje się że niektóre węzły wychodzą poza nasze dwuwymiarowe miasto, nie interesują nas one, dlatego musimy przefiltrować je tak żeby zostały tylko te węzły które są w interesującym nas regionie, zrobimy to za pomocą funkcji filter.

def fits(antinode: complex) -> bool:
    a = antinode.real
    b = antinode.imag
    return a >= 0 and b >= 0 and a <= x and b <= y


print(len(list(filter(fits, antinodes))))

Po uruchomieniu kodu dostajemy poprawny wynik.

Część druga

Do zdobycia drugiej gwiazdki nie musimy zmieniać wiele, okazuje się, że jednak węzły leżą na całej długości linii przechodzącej przez wieże z tymi samymi częstotliwościami.

T....#....
...T......
.T....#...
.........#
..#.......
..........
...#......

Na powyższym przykładzie można zauważyć o co chodzi. W tej części liczymy także wieże jako punkty będące węzłami, dlatego w powyższym przykładzie mamy 8 węzłów.

Rozwiązanie

Należy zmienić nieco kod w naszej pętli szukającej węzły.

W pętli while będziemy podejmować kolejne kroki o wielkości takiej jak dystans pomiędzy dwoma antenami, najpierw w jedną stronę (aż dojdziemy do końca miasta), a potem w drugą stronę, w ten sposób dodamy także anteny jako nasze węzły.

for frequency, positions in antenas.items():
    for a, b in combinations(positions, 2):
        diff = a - b
        cur = copy(a)
        while fits(cur):
            antinodes.add(cur)
            cur += diff

        cur = copy(b)
        while fits(cur):
            antinodes.add(cur)
            cur -= diff

print(len(antinodes))

Ta prosta zmiana wystarcza żeby rozwiązać zadanie w bardzo krótkim czasie.

Podsumowanie

Cieszę się że kolejne zadanie poszło w miarę szybko, jak przeczytałem treść na początku to byłem prawie że przekonany że naiwne rozwiązanie nie wystarczy, jednak wejście okazało się nie być dużym i niepotrzebne były żadne dodatkowe optymalizacje.