#! /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()