This commit is contained in:
akp 2023-12-13 14:37:44 +00:00
parent d1186bb5e3
commit 5415902437
No known key found for this signature in database
GPG key ID: CF8D58F3DEB20755
7 changed files with 209 additions and 15 deletions

View file

@ -0,0 +1,9 @@
# [Day 13: Point of Incidence](https://adventofcode.com/2023/day/13)
I've got a function that checks vertical lines of symmetry, and to check horizontal ones I just swap the `(x, y)` coords in the grid to `(y, x)` then run it again.
For each candidate line of symmetry, work out a range that values we need to check (based on which side goes out of bounds first), then for each row in the full height of the grid, count how many rocks are `x` steps away from the line (eg. `.#.|.#.` has 2 rocks 1 step away, assuming the pipe is the line we're checking around).
For part 1, we just check that no single row has any step within it that doesn't have 0 or 2 rocks.
For part 2, we make the same check but allow any grid where only a single row fouls that condition. This gives us multiple candidate lines of symmetry for each grid, so we diff it with the as-is case from part 1 and use whatever is new.

View file

@ -0,0 +1,152 @@
import sys
import gridutil.grid as gu
from collections import defaultdict
from typing import Optional, Callable, Iterable
def parse(instr: str) -> list[gu.Grid]:
res = []
for block in instr.split("\n\n"):
res.append(gu.parse(block, filter_fn=lambda x: x != "."))
return res
Acceptor = Callable[[Iterable[int]], bool]
def find_vertical_reflections(
grid: gu.Grid, make_accept_fn: Callable[[], Acceptor]
) -> set[int]:
max_x = gu.get_max_x(grid)
max_y = gu.get_max_y(grid)
res = set()
for base_x in range(max_x):
acceptable = True
accept_fn = make_accept_fn()
diff = min(max_x - base_x, base_x + 1)
range_from = max(0, base_x + 1 - diff)
range_to = min(max_x, base_x + diff) + 1
for y in range(max_y + 1):
dists = defaultdict(lambda: 0)
for x in range(range_from, range_to):
if grid.get((x, y)) is None:
continue
if x > base_x:
key = x - base_x - 1
else:
key = base_x - x
dists[key] = dists[key] + 1
if not accept_fn(dists.values()):
acceptable = False
break
if acceptable:
res.add(base_x)
return res
def make_standard_acceptor() -> Acceptor:
def inner(x: Iterable[int]) -> bool:
for v in x:
if v != 2:
return False
return True
return inner
def make_smudge_acceptor() -> Acceptor:
n = 0
def inner(x: Iterable[int]) -> bool:
nonlocal n
for v in x:
if v != 2:
if n == 1:
return False
n += 1
return True
return inner
def swap_x_y(grid: gu.Grid) -> gu.Grid:
res = {}
for (x, y) in grid:
res[(y, x)] = grid[(x, y)]
return res
def set_to_option(x: set[int]) -> Optional[int]:
assert len(x) <= 1
if len(x) == 0:
return None
return x.pop()
def one(instr: str):
grids = parse(instr)
acc = 0
for grid in grids:
v = find_vertical_reflections(grid, make_standard_acceptor)
h = find_vertical_reflections(swap_x_y(grid), make_standard_acceptor)
if (v := set_to_option(v)) is not None:
acc += v + 1
if (h := set_to_option(h)) is not None:
acc += (h + 1) * 100
return acc
def two(instr: str):
grids = parse(instr)
acc = 0
for grid in grids:
v = find_vertical_reflections(
grid, make_smudge_acceptor
) - find_vertical_reflections(grid, make_standard_acceptor)
sgrid = swap_x_y(grid)
h = find_vertical_reflections(
sgrid, make_smudge_acceptor
) - find_vertical_reflections(sgrid, make_standard_acceptor)
if (v := set_to_option(v)) is not None:
acc += v + 1
if (h := set_to_option(h)) is not None:
acc += (h + 1) * 100
return acc
def _debug(*args, **kwargs):
kwargs["file"] = sys.stderr
print(*args, **kwargs)
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]:
print("Missing day argument", file=sys.stderr)
sys.exit(1)
inp = sys.stdin.read().strip()
if sys.argv[1] == "1":
print(one(inp))
else:
print(two(inp))

View file

@ -0,0 +1,14 @@
{
"1": [
{
"is": "405",
"input": "#.##..##.\n..#.##.#.\n##......#\n##......#\n..#.##.#.\n..##..##.\n#.#.##.#.\n\n#...##..#\n#....#..#\n..##..###\n#####.##.\n#####.##.\n..##..###\n#....#..#\n"
}
],
"2": [
{
"is": "400",
"input": "#.##..##.\n..#.##.#.\n##......#\n##......#\n..#.##.#.\n..##..##.\n#.#.##.#.\n\n#...##..#\n#....#..#\n..##..###\n#####.##.\n#####.##.\n..##..###\n#....#..#\n"
}
]
}

View file

@ -22,3 +22,4 @@ Solutions to the [2023 Advent of Code](https://adventofcode.com/2023).
| 10 - Pipe Maze | ★ ★ | Python | Thoroughly barked up two wrong trees then Googled an algo based on a thought and oops it just works? | | 10 - Pipe Maze | ★ ★ | Python | Thoroughly barked up two wrong trees then Googled an algo based on a thought and oops it just works? |
| 11 - Cosmic Expansion | ★ ★ | Python | Djikstra's and A* are the wrong way to do this (I tried both before engaging my brain) and then had to optimise various things for part 2 but nothing was horrendous. | | 11 - Cosmic Expansion | ★ ★ | Python | Djikstra's and A* are the wrong way to do this (I tried both before engaging my brain) and then had to optimise various things for part 2 but nothing was horrendous. |
| 12 - Hot Springs | ★ ★ | Python | Some hints from the subreddit were needed but they got me well on my way to completing this. Memoisation is kind, long live memoisation. | | 12 - Hot Springs | ★ ★ | Python | Some hints from the subreddit were needed but they got me well on my way to completing this. Memoisation is kind, long live memoisation. |
| 13 - Point of Incidence | ★ ★ | Python | This one made me feel slightly intelligent. |

View file

@ -1 +1,2 @@
from .grid import Coordinate, Grid, add_coords, parse import gridutil.grid
import gridutil.coord

13
gridutil/coord.py Normal file
View file

@ -0,0 +1,13 @@
Coordinate = tuple[int, int]
def add(a: Coordinate, b: Coordinate) -> Coordinate:
xa, ya = a
xb, yb = b
return xa + xb, ya + yb
def sub(a: Coordinate, b: Coordinate) -> Coordinate:
xa, ya = a
xb, yb = b
return xa - xb, ya - yb

View file

@ -1,20 +1,14 @@
from typing import TypeVar, Callable, Optional from typing import TypeVar, Callable, Optional
import gridutil.coord as coord
T = TypeVar("T") T = TypeVar("T")
Grid = dict[coord.Coordinate, T]
Coordinate = tuple[int, int]
Grid = dict[Coordinate, T]
def add_coords(a: Coordinate, b: Coordinate) -> Coordinate:
xa, ya = a
xb, yb = b
return xa + xb, ya + yb
def parse(instr: str, filter_fn: Optional[Callable[[str], bool]] = None) -> Grid: def parse(instr: str, filter_fn: Optional[Callable[[str], bool]] = None) -> Grid:
if filter is None: if filter_fn is None:
filter = lambda _: True filter_fn = lambda _: True
res = {} res = {}
for y, line in enumerate(instr.splitlines()): for y, line in enumerate(instr.splitlines()):
@ -25,7 +19,9 @@ def parse(instr: str, filter_fn: Optional[Callable[[str], bool]] = None) -> Grid
return res return res
def _get_max(grid: Grid, idx: str, filter_fn: Optional[Callable[[T], bool]] = None) -> int: def _get_max(
grid: Grid, idx: str, filter_fn: Optional[Callable[[T], bool]] = None
) -> int:
g = grid g = grid
if filter is not None: if filter is not None:
g = filter(filter_fn, grid) g = filter(filter_fn, grid)
@ -38,3 +34,11 @@ def get_max_x(grid: Grid, filter_fn: Optional[Callable[[T], bool]] = None) -> in
def get_max_y(grid: Grid, filter_fn: Optional[Callable[[T], bool]] = None) -> int: def get_max_y(grid: Grid, filter_fn: Optional[Callable[[T], bool]] = None) -> int:
return _get_max(grid, 1, filter_fn=filter_fn) return _get_max(grid, 1, filter_fn=filter_fn)
def print_grid(grid: Grid, **kwargs):
for y in range(get_max_y(grid) + 1):
for x in range(get_max_x(grid) + 1):
v = grid.get((x, y), " ")
print(v, end="", **kwargs)
print(**kwargs)