2018: d17: ex1: add solution
This commit is contained in:
parent
69b8894c54
commit
dd31b1369d
108
2018/d17/ex1/ex1.py
Executable file
108
2018/d17/ex1/ex1.py
Executable file
|
@ -0,0 +1,108 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class Point(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
|
||||||
|
class Direction(enum.Enum):
|
||||||
|
DOWN = Point(0, 1)
|
||||||
|
LEFT = Point(-1, 0)
|
||||||
|
RIGHT = Point(1, 0)
|
||||||
|
|
||||||
|
def apply(self, p: Point) -> Point:
|
||||||
|
dx, dy = self.value
|
||||||
|
return Point(p.x + dx, p.y + dy)
|
||||||
|
|
||||||
|
|
||||||
|
def solve(input: str) -> int:
|
||||||
|
def parse_range(input: str) -> range:
|
||||||
|
if ".." not in input:
|
||||||
|
input = input + ".." + input
|
||||||
|
start, end = map(int, input.split(".."))
|
||||||
|
return range(start, end + 1)
|
||||||
|
|
||||||
|
def parse_line(input: str) -> Iterator[Point]:
|
||||||
|
xs, ys = sorted(input.split(", "))
|
||||||
|
yield from map(
|
||||||
|
Point._make,
|
||||||
|
itertools.product(parse_range(xs[2:]), parse_range(ys[2:])),
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse(input: list[str]) -> set[Point]:
|
||||||
|
return {p for line in input for p in parse_line(line)}
|
||||||
|
|
||||||
|
def flow(clay: set[Point], source: Point) -> set[Point]:
|
||||||
|
max_y = max(p.y for p in clay)
|
||||||
|
|
||||||
|
def helper(
|
||||||
|
source: Point,
|
||||||
|
water: set[Point],
|
||||||
|
settled: set[Point],
|
||||||
|
direction: Direction = Direction.DOWN,
|
||||||
|
) -> bool:
|
||||||
|
# Clay is considered "settled"
|
||||||
|
if source in clay:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# We've already seen this, return early
|
||||||
|
if source in water:
|
||||||
|
return source in settled
|
||||||
|
|
||||||
|
# Account for this new source
|
||||||
|
water.add(source)
|
||||||
|
|
||||||
|
below = Direction.DOWN.apply(source)
|
||||||
|
if below not in clay:
|
||||||
|
if below.y <= max_y:
|
||||||
|
helper(below, water, settled)
|
||||||
|
if below not in settled:
|
||||||
|
return False
|
||||||
|
|
||||||
|
left = Direction.LEFT.apply(source)
|
||||||
|
right = Direction.RIGHT.apply(source)
|
||||||
|
l_filled = helper(left, water, settled, Direction.LEFT)
|
||||||
|
r_filled = helper(right, water, settled, Direction.RIGHT)
|
||||||
|
|
||||||
|
if direction == Direction.DOWN and l_filled and r_filled:
|
||||||
|
settled.add(source)
|
||||||
|
while left in water:
|
||||||
|
settled.add(left)
|
||||||
|
left = Direction.LEFT.apply(left)
|
||||||
|
while right in water:
|
||||||
|
settled.add(right)
|
||||||
|
right = Direction.RIGHT.apply(right)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return (direction == Direction.LEFT and l_filled) or (
|
||||||
|
direction == Direction.RIGHT and r_filled
|
||||||
|
)
|
||||||
|
|
||||||
|
assert source not in clay # Sanity check
|
||||||
|
water: set[Point] = set()
|
||||||
|
settled: set[Point] = set()
|
||||||
|
helper(source, water, settled)
|
||||||
|
assert settled <= water # Sanity check
|
||||||
|
return water
|
||||||
|
|
||||||
|
clay = parse(input.splitlines())
|
||||||
|
sys.setrecursionlimit(5000) # HACK
|
||||||
|
water = flow(clay, Point(500, 0))
|
||||||
|
min_y, max_y = min(p.y for p in clay), max(p.y for p in clay)
|
||||||
|
return sum(min_y <= p.y <= max_y for p in water)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
input = sys.stdin.read()
|
||||||
|
print(solve(input))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in a new issue