137 lines
3.3 KiB
Python
Executable File
137 lines
3.3 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
from typing import Iterable
|
|
# from typing import Tuple
|
|
from typing import Set
|
|
from typing import Dict
|
|
# from typing import Optional
|
|
|
|
import itertools
|
|
from collections import Counter
|
|
from collections import namedtuple
|
|
|
|
|
|
Point = namedtuple("Point", "x y")
|
|
|
|
D2P = {
|
|
"se": Point(1, -1),
|
|
"sw": Point(-1, -1),
|
|
"ne": Point(1, 1),
|
|
"nw": Point(-1, 1),
|
|
"w": Point(-2, 0),
|
|
"e": Point(2, 0),
|
|
}
|
|
|
|
|
|
def directions_to_point(s: str) -> Point:
|
|
point = Point(0, 0)
|
|
direction = ""
|
|
for c in s:
|
|
direction += c
|
|
if direction in D2P:
|
|
point = add_points(point, D2P[direction])
|
|
direction = ""
|
|
|
|
if direction != "":
|
|
raise ValueError("Something left over unexpectedly")
|
|
|
|
return point
|
|
|
|
|
|
def add_points(p1, p2) -> Point:
|
|
return Point(
|
|
p1.x + p2.x,
|
|
p1.y + p2.y,
|
|
)
|
|
|
|
|
|
def neighbors(point: Point) -> Iterable[Point]:
|
|
for diff in D2P.values():
|
|
yield add_points(point, diff)
|
|
|
|
|
|
def neighbor_counts(points: Set[Point]) -> Dict[Point, int]:
|
|
# return {
|
|
# p: sum(map(lambda x: 1 if x in points else 0, neighbors(p)))
|
|
# for p in points
|
|
# }
|
|
return Counter(itertools.chain.from_iterable(map(neighbors, points)))
|
|
|
|
|
|
def next_generation(points: Set[Point]):
|
|
counts = neighbor_counts(points)
|
|
return (
|
|
{p for p in points if counts[p] in (1, 2)} |
|
|
{p for p in counts if p not in points and counts[p] == 2}
|
|
)
|
|
|
|
|
|
def point_deltas() -> Iterable[Point]:
|
|
return D2P.values()
|
|
|
|
|
|
def life(points: Set[Point], n: int) -> Set[Point]:
|
|
for r in range(n):
|
|
points = next_generation(points)
|
|
|
|
return points
|
|
|
|
|
|
def sample():
|
|
black: Set[Point] = set()
|
|
for line in (
|
|
"sesenwnenenewseeswwswswwnenewsewsw",
|
|
"neeenesenwnwwswnenewnwwsewnenwseswesw",
|
|
"seswneswswsenwwnwse",
|
|
"nwnwneseeswswnenewneswwnewseswneseene",
|
|
"swweswneswnenwsewnwneneseenw",
|
|
"eesenwseswswnenwswnwnwsewwnwsene",
|
|
"sewnenenenesenwsewnenwwwse",
|
|
"wenwwweseeeweswwwnwwe",
|
|
"wsweesenenewnwwnwsenewsenwwsesesenwne",
|
|
"neeswseenwwswnwswswnw",
|
|
"nenwswwsewswnenenewsenwsenwnesesenew",
|
|
"enewnwewneswsewnwswenweswnenwsenwsw",
|
|
"sweneswneswneneenwnewenewwneswswnese",
|
|
"swwesenesewenwneswnwwneseswwne",
|
|
"enesenwswwswneneswsenwnewswseenwsese",
|
|
"wnwnesenesenenwwnenwsewesewsesesew",
|
|
"nenewswnwewswnenesenwnesewesw",
|
|
"eneswnwswnwsenenwnwnwwseeswneewsenese",
|
|
"neswnwewnwnwseenwseesewsenwsweewe",
|
|
"wseweeenwnesenwwwswnew",
|
|
):
|
|
p = directions_to_point(line)
|
|
if p in black:
|
|
black.remove(p)
|
|
else:
|
|
black.add(p)
|
|
|
|
print(f"Num black is {len(black)}")
|
|
|
|
rounds = 100
|
|
black = life(black, rounds)
|
|
print(f"Num black after {rounds} is {len(black)}")
|
|
|
|
|
|
def part2():
|
|
black: Set[Point] = set()
|
|
with open("input.txt") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
p = directions_to_point(line)
|
|
if p in black:
|
|
black.remove(p)
|
|
else:
|
|
black.add(p)
|
|
|
|
print(f"Num black is {len(black)}")
|
|
|
|
rounds = 100
|
|
black = life(black, rounds)
|
|
print(f"Num black after {rounds} is {len(black)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# sample()
|
|
part2()
|