mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Compare commits
5 Commits
b41571949e
...
b23676bf04
| Author | SHA1 | Date | |
|---|---|---|---|
| b23676bf04 | |||
| 40632c8114 | |||
| 073b576fd8 | |||
| f3a3e1fca3 | |||
| 4a479c1646 |
23
2024/bonus/day25/main.tf
Normal file
23
2024/bonus/day25/main.tf
Normal file
@@ -0,0 +1,23 @@
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
locals {
|
||||
blocks = split("\n\n", chomp(var.input))
|
||||
heights = [
|
||||
for block in local.blocks : [
|
||||
for i in range(5) : length([
|
||||
for line in split("\n", block) : line if substr(line, i, 1) == "#"
|
||||
])
|
||||
]
|
||||
]
|
||||
|
||||
locks = [for i in range(length(local.blocks)) : local.heights[i] if startswith(local.blocks[i], "#####")]
|
||||
keys = [for i in range(length(local.blocks)) : local.heights[i] if !startswith(local.blocks[i], "#####")]
|
||||
|
||||
combined = concat([for lock in local.locks : [for key in local.keys : [for i in range(5) : lock[i] + key[i] <= 7]]]...)
|
||||
}
|
||||
|
||||
output "part1" {
|
||||
value = length([for combination in local.combined : combination if alltrue(combination)])
|
||||
}
|
||||
@@ -87,3 +87,12 @@ module "day19" {
|
||||
output "day19_1" {
|
||||
value = module.day19.part1
|
||||
}
|
||||
|
||||
module "day25" {
|
||||
source = "./day25"
|
||||
input = file("../inputs/25.txt")
|
||||
}
|
||||
|
||||
output "day25_1" {
|
||||
value = module.day25.part1
|
||||
}
|
||||
|
||||
@@ -164,3 +164,20 @@ run "day19" {
|
||||
error_message = "Part1 output is wrong"
|
||||
}
|
||||
}
|
||||
|
||||
run "day25" {
|
||||
command = plan
|
||||
|
||||
module {
|
||||
source = "./day25"
|
||||
}
|
||||
|
||||
variables {
|
||||
input = file("../tests/samples/25.txt")
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = output.part1 == 3
|
||||
error_message = "Part1 output is wrong"
|
||||
}
|
||||
}
|
||||
|
||||
159
2024/src/aoc/days/day21.py
Normal file
159
2024/src/aoc/days/day21.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import functools
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
from . import SeparateRunner
|
||||
|
||||
NUMPAD = {
|
||||
"A": (3, 2),
|
||||
"0": (3, 1),
|
||||
"1": (2, 0),
|
||||
"2": (2, 1),
|
||||
"3": (2, 2),
|
||||
"4": (1, 0),
|
||||
"5": (1, 1),
|
||||
"6": (1, 2),
|
||||
"7": (0, 0),
|
||||
"8": (0, 1),
|
||||
"9": (0, 2),
|
||||
}
|
||||
|
||||
DIRPAD = {
|
||||
"A": (0, 2),
|
||||
"^": (0, 1),
|
||||
"<": (1, 0),
|
||||
"v": (1, 1),
|
||||
">": (1, 2),
|
||||
}
|
||||
|
||||
|
||||
@functools.cache
|
||||
def shortest_numpad(from_: str, to: str) -> list[str]:
|
||||
inverse = set(NUMPAD.values())
|
||||
ay, ax = NUMPAD[from_]
|
||||
by, bx = NUMPAD[to]
|
||||
|
||||
dx, dy = bx - ax, by - ay
|
||||
|
||||
sx = "<" if dx < 0 else ">"
|
||||
sy = "^" if dy < 0 else "v"
|
||||
|
||||
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
|
||||
return abs(dy) * sy + abs(dx) * sx + "A"
|
||||
else:
|
||||
return abs(dx) * sx + abs(dy) * sy + "A"
|
||||
|
||||
|
||||
@functools.cache
|
||||
def shortest_dirpad(from_: str, to: str) -> str:
|
||||
inverse = set(DIRPAD.values())
|
||||
ay, ax = DIRPAD[from_]
|
||||
by, bx = DIRPAD[to]
|
||||
|
||||
dx, dy = bx - ax, by - ay
|
||||
sx = "<" if dx < 0 else ">"
|
||||
sy = "^" if dy < 0 else "v"
|
||||
|
||||
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
|
||||
return abs(dy) * sy + abs(dx) * sx + "A"
|
||||
else:
|
||||
return abs(dx) * sx + abs(dy) * sy + "A"
|
||||
|
||||
|
||||
def encode_shortest_numpad(code: str) -> str:
|
||||
pos = "A"
|
||||
|
||||
res = ""
|
||||
|
||||
for c in code:
|
||||
res += shortest_numpad(pos, c)
|
||||
# print(c, res)
|
||||
pos = c
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def encode_shortest_dirpad(code: str) -> str:
|
||||
pos = "A"
|
||||
|
||||
res = ""
|
||||
|
||||
for c in code:
|
||||
if pos != c:
|
||||
res += shortest_dirpad(pos, c)
|
||||
else:
|
||||
res += "A"
|
||||
pos = c
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def decode(code: str, pad: dict[str, tuple[int, int]]) -> str:
|
||||
result = ""
|
||||
inverse = {v: k for k, v in pad.items()}
|
||||
|
||||
y, x = pad["A"]
|
||||
|
||||
for i, c in enumerate(code):
|
||||
match c:
|
||||
case "A":
|
||||
result += inverse[y, x]
|
||||
case "^":
|
||||
y -= 1
|
||||
case "v":
|
||||
y += 1
|
||||
case "<":
|
||||
x -= 1
|
||||
case ">":
|
||||
x += 1
|
||||
|
||||
if (y, x) not in inverse:
|
||||
raise ValueError(
|
||||
f"""Moved off the board {x, y}, after processing {c}.
|
||||
Path so far: {result} (from {code[:i]})"""
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def count_steps(path: str, count: int) -> dict[str, int]:
|
||||
cur = "A"
|
||||
counts = defaultdict(int)
|
||||
|
||||
for c in path:
|
||||
step = shortest_dirpad(cur, c)
|
||||
cur = c
|
||||
counts[step] += count
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
class DayRunner(SeparateRunner):
|
||||
@classmethod
|
||||
def part1(cls, input: str) -> int:
|
||||
result = 0
|
||||
for code in input.strip().split("\n"):
|
||||
numpad = encode_shortest_numpad(code)
|
||||
robot1 = encode_shortest_dirpad(numpad)
|
||||
robot2 = encode_shortest_dirpad(robot1)
|
||||
|
||||
result += int(code[:-1]) * len(robot2)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def part2(cls, input: str, robots=25) -> int:
|
||||
result = 0
|
||||
for code in input.strip().split("\n"):
|
||||
numpad = encode_shortest_numpad(code)
|
||||
keypresses = Counter([numpad])
|
||||
|
||||
for _ in range(robots + 1):
|
||||
new_presses = Counter()
|
||||
for subroute, count in keypresses.items():
|
||||
new_presses.update(count_steps(subroute, count))
|
||||
|
||||
keypresses = new_presses
|
||||
|
||||
result += int(code[:-1]) * keypresses.total()
|
||||
|
||||
return result
|
||||
29
2024/src/aoc/days/day25.py
Normal file
29
2024/src/aoc/days/day25.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import numpy
|
||||
|
||||
from . import CombinedRunner
|
||||
|
||||
|
||||
class DayRunner(CombinedRunner):
|
||||
@classmethod
|
||||
def run_both(cls, input: str) -> tuple[int, None]:
|
||||
blocks = input.strip().split("\n\n")
|
||||
|
||||
keys = []
|
||||
locks = []
|
||||
|
||||
for block in blocks:
|
||||
grid = numpy.array(list(map(list, block.splitlines())))
|
||||
heights = numpy.count_nonzero(grid == "#", axis=0)
|
||||
|
||||
if block.startswith("#####"):
|
||||
locks.append(heights)
|
||||
else:
|
||||
keys.append(heights)
|
||||
|
||||
locks = numpy.stack(locks, axis=0)
|
||||
|
||||
fitting = sum(
|
||||
numpy.count_nonzero(numpy.all((key + locks) <= 7, axis=1)) for key in keys
|
||||
)
|
||||
|
||||
return fitting, None
|
||||
5
2024/tests/samples/21.txt
Normal file
5
2024/tests/samples/21.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
029A
|
||||
980A
|
||||
179A
|
||||
456A
|
||||
379A
|
||||
39
2024/tests/samples/25.txt
Normal file
39
2024/tests/samples/25.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
#####
|
||||
.####
|
||||
.####
|
||||
.####
|
||||
.#.#.
|
||||
.#...
|
||||
.....
|
||||
|
||||
#####
|
||||
##.##
|
||||
.#.##
|
||||
...##
|
||||
...#.
|
||||
...#.
|
||||
.....
|
||||
|
||||
.....
|
||||
#....
|
||||
#....
|
||||
#...#
|
||||
#.#.#
|
||||
#.###
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
#.#..
|
||||
###..
|
||||
###.#
|
||||
###.#
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
.....
|
||||
#....
|
||||
#.#..
|
||||
#.#.#
|
||||
#####
|
||||
55
2024/tests/test_day21.py
Normal file
55
2024/tests/test_day21.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import pytest
|
||||
|
||||
from aoc.days.day21 import (
|
||||
DayRunner,
|
||||
encode_shortest_dirpad,
|
||||
encode_shortest_numpad,
|
||||
)
|
||||
|
||||
from . import get_data
|
||||
|
||||
|
||||
def test_encode_shortest_numpad() -> None:
|
||||
assert encode_shortest_numpad("029A") in (
|
||||
"<A^A>^^AvvvA",
|
||||
"<A^A^>^AvvvA",
|
||||
"<A^A^^>AvvvA",
|
||||
)
|
||||
|
||||
|
||||
def test_encode_shortest_dirpad() -> None:
|
||||
numpad_encoded = encode_shortest_numpad("029A")
|
||||
assert len(encode_shortest_dirpad(numpad_encoded)) == len(
|
||||
"v<<A>>^A<A>AvA<^AA>A<vAAA>^A"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"code,answer",
|
||||
[
|
||||
(
|
||||
"029A",
|
||||
"<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A",
|
||||
),
|
||||
("980A", "<v<A>>^AAAvA^A<vA<AA>>^AvAA<^A>A<v<A>A>^AAAvA<^A>A<vA>^A<A>A"),
|
||||
(
|
||||
"179A",
|
||||
"<v<A>>^A<vA<A>>^AAvAA<^A>A<v<A>>^AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A",
|
||||
),
|
||||
("456A", "<v<A>>^AA<vA<A>>^AAvAA<^A>A<vA>^A<A>A<vA>^A<A>A<v<A>A>^AAvA<^A>A"),
|
||||
("379A", "<v<A>>^AvA^A<vA<AA>>^AAvA<^A>AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A"),
|
||||
],
|
||||
)
|
||||
def test_encode_shortest_dirpad_twice(code: str, answer: str) -> None:
|
||||
numpad_encoded = encode_shortest_numpad(code)
|
||||
robot1 = encode_shortest_dirpad(numpad_encoded)
|
||||
robot2 = encode_shortest_dirpad(robot1)
|
||||
assert len(robot2) == len(answer)
|
||||
|
||||
|
||||
def test_sample_part1() -> None:
|
||||
assert DayRunner.part1(get_data(21)) == 126384
|
||||
|
||||
|
||||
def test_sample_part2() -> None:
|
||||
assert DayRunner.part2(get_data(21), robots=2) == 126384
|
||||
7
2024/tests/test_day25.py
Normal file
7
2024/tests/test_day25.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from aoc.days.day25 import DayRunner
|
||||
|
||||
from . import get_data
|
||||
|
||||
|
||||
def test_sample_part1() -> None:
|
||||
assert DayRunner.part1(get_data(25)) == 3
|
||||
Reference in New Issue
Block a user