2023.13
This commit is contained in:
parent
d1186bb5e3
commit
5415902437
7 changed files with 209 additions and 15 deletions
9
challenges/2023/13-pointOfIncidence/README.md
Normal file
9
challenges/2023/13-pointOfIncidence/README.md
Normal 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.
|
152
challenges/2023/13-pointOfIncidence/main.py
Normal file
152
challenges/2023/13-pointOfIncidence/main.py
Normal 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))
|
14
challenges/2023/13-pointOfIncidence/tests.json
Normal file
14
challenges/2023/13-pointOfIncidence/tests.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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. |
|
|
@ -1 +1,2 @@
|
||||||
from .grid import Coordinate, Grid, add_coords, parse
|
import gridutil.grid
|
||||||
|
import gridutil.coord
|
||||||
|
|
13
gridutil/coord.py
Normal file
13
gridutil/coord.py
Normal 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
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue