Compare commits

...

25 Commits

Author SHA1 Message Date
93e365d10b day 25 puzzle 1 2024-12-25 19:14:02 +01:00
9300d786f8 day 24 puzzle 1 2024-12-25 18:41:08 +01:00
ec75800078 implemented bron-kerbosch v2 2024-12-23 12:28:17 +01:00
16a0d9b3f8 day 23 puzzle 2 2024-12-23 12:19:42 +01:00
d830919adc day 23 puzzle 1 2024-12-23 11:43:26 +01:00
056e29b96c day 22 puzzle 1 2024-12-23 11:21:19 +01:00
47fbeb1ce9 started day 21 puzzle 1 2024-12-23 11:15:37 +01:00
4826c0d21e day 20 puzzle 1 2024-12-20 22:31:33 +01:00
0950e76d8d day 19 puzzle 2 2024-12-20 09:53:58 +01:00
5921a90c73 day 19 puzzle 1 2024-12-19 18:26:07 +01:00
4a429bd62b removed page number on first page and progress.png 2024-12-18 21:10:32 +01:00
953716b95a day 18 puzzle 2 2024-12-18 21:07:30 +01:00
43c6d9e325 day 18 puzzle 1 2024-12-18 20:42:18 +01:00
927feb071e started day 17 puzzle 2 2024-12-18 14:32:38 +01:00
5107cf7f82 started day 16 puzzle 1 2024-12-18 14:31:57 +01:00
9a7eef5186 day 17 puzzle 1 2024-12-18 08:12:37 +01:00
dd1e8a81fd day 15 puzzle 2 2024-12-16 18:29:29 +01:00
05f1f99ac1 day 15 puzzle 1 2024-12-16 12:40:42 +01:00
befa6403ba day 14 puzzle 2 (python) 2024-12-15 14:12:08 +01:00
ab60a501f5 day 14 puzzle 2 2024-12-15 14:11:50 +01:00
6ab8ef6d26 day 14 puzzle 1 2024-12-15 12:03:32 +01:00
ad4adc2372 day 13 puzzle 2 2024-12-13 13:19:44 +01:00
6caa3516e6 day 13 puzzle 1 2024-12-13 12:27:51 +01:00
c23ff2ea4b day 12 puzzle 2 2024-12-13 00:42:58 +01:00
60a9ee3d2d day 12 puzzle 1 2024-12-12 10:23:23 +01:00
56 changed files with 2335 additions and 2 deletions

75
day14.py Normal file
View File

@ -0,0 +1,75 @@
import re
import pygame
with open("res/inputs/day14.txt", "r") as f:
lines = f.read().split("\n")
bots = []
for line in lines:
m = re.match("^p=(.*?),(.*?) v=(.*?),(.*?)$", line)
bot = {
"pos": {
"x": int(m.group(1)),
"y": int(m.group(2)),
},
"vel": {
"x": int(m.group(3)),
"y": int(m.group(4)),
}
}
bots.append(bot)
W = 101
H = 103
pygame.init()
win = pygame.display.set_mode([W, H])
def display():
win.fill(0)
for bot in bots:
win.set_at((bot["pos"]["x"], bot["pos"]["y"]), (255, 255, 255))
pygame.display.flip()
def move(f):
for bot in bots:
bot["pos"]["x"] = (bot["pos"]["x"] + bot["vel"]["x"] * f) % W
bot["pos"]["y"] = (bot["pos"]["y"] + bot["vel"]["y"] * f) % H
clock = pygame.time.Clock()
running = True
step = 0
update = True
auto = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_RIGHT:
step += 1
move(1)
update = True
elif event.key == pygame.K_LEFT:
step -= 1
move(-1)
update = True
elif event.key == pygame.K_SPACE:
auto = not auto
if auto:
move(1)
step += 1
update = True
if update:
print(step)
pygame.display.set_caption(f"Day 14 - step {step} - {clock.get_fps():.2f}fps")
display()
update = False
clock.tick(20)

26
day22.py Normal file
View File

@ -0,0 +1,26 @@
with open("res/inputs/day22.txt", "r") as f:
values = list(map(int, f.read().split("\n")))
calls = 0
def step(a, shift):
global calls
calls += 1
s = (a >> -shift) if shift < 0 else (a << shift)
return (s ^ a) & 0xffffff
def rand(value):
b = step(value, 6)
c = step(b, -5)
d = step(c, 11)
return d
total = 0
for v in values:
for _ in range(2000):
v = rand(v)
total += v
print(total)
print(calls)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 135 KiB

View File

@ -19,4 +19,32 @@
10:
stars: 2
11:
stars: 2
stars: 2
12:
stars: 2
13:
stars: 2
14:
stars: 2
15:
stars: 2
16:
stars: 0
17:
stars: 1
18:
stars: 2
19:
stars: 2
20:
stars: 1
21:
stars: 0
22:
stars: 1
23:
stars: 2
24:
stars: 1
25:
stars: 1

4
res/examples/day12_1.txt Normal file
View File

@ -0,0 +1,4 @@
AAAA
BBCD
BBCC
EEEC

5
res/examples/day12_2.txt Normal file
View File

@ -0,0 +1,5 @@
OOOOO
OXOXO
OOOOO
OXOXO
OOOOO

10
res/examples/day12_3.txt Normal file
View File

@ -0,0 +1,10 @@
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE

5
res/examples/day12_4.txt Normal file
View File

@ -0,0 +1,5 @@
EEEEE
EXXXX
EEEEE
EXXXX
EEEEE

6
res/examples/day12_5.txt Normal file
View File

@ -0,0 +1,6 @@
AAAAAA
AAABBA
AAABBA
ABBAAA
ABBAAA
AAAAAA

15
res/examples/day13.txt Normal file
View File

@ -0,0 +1,15 @@
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176
Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450
Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279

12
res/examples/day14.txt Normal file
View File

@ -0,0 +1,12 @@
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3

10
res/examples/day15_1.txt Normal file
View File

@ -0,0 +1,10 @@
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv<v>>v<<

21
res/examples/day15_2.txt Normal file
View File

@ -0,0 +1,21 @@
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^

15
res/examples/day16_1.txt Normal file
View File

@ -0,0 +1,15 @@
###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############

17
res/examples/day16_2.txt Normal file
View File

@ -0,0 +1,17 @@
#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################

5
res/examples/day17_1.txt Normal file
View File

@ -0,0 +1,5 @@
Register A: 729
Register B: 0
Register C: 0
Program: 0,1,5,4,3,0

5
res/examples/day17_2.txt Normal file
View File

@ -0,0 +1,5 @@
Register A: 2024
Register B: 0
Register C: 0
Program: 0,3,5,4,3,0

25
res/examples/day18.txt Normal file
View File

@ -0,0 +1,25 @@
5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0

10
res/examples/day19.txt Normal file
View File

@ -0,0 +1,10 @@
r, wr, b, g, bwu, rb, gb, br
brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb

15
res/examples/day20.txt Normal file
View File

@ -0,0 +1,15 @@
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############

5
res/examples/day21.txt Normal file
View File

@ -0,0 +1,5 @@
029A
980A
179A
456A
379A

1
res/examples/day22.txt Normal file
View File

@ -0,0 +1 @@
123

32
res/examples/day23.txt Normal file
View File

@ -0,0 +1,32 @@
kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn

10
res/examples/day24_1.txt Normal file
View File

@ -0,0 +1,10 @@
x00: 1
x01: 1
x02: 1
y00: 0
y01: 1
y02: 0
x00 AND y00 -> z00
x01 XOR y01 -> z01
x02 OR y02 -> z02

47
res/examples/day24_2.txt Normal file
View File

@ -0,0 +1,47 @@
x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1
ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj

39
res/examples/day25.txt Normal file
View File

@ -0,0 +1,39 @@
#####
.####
.####
.####
.#.#.
.#...
.....
#####
##.##
.#.##
...##
...#.
...#.
.....
.....
#....
#....
#...#
#.#.#
#.###
#####
.....
.....
#.#..
###..
###.#
###.#
#####
.....
.....
.....
#....
#.#..
#.#.#
#####

68
src/day12/puzzle1.typ Normal file
View File

@ -0,0 +1,68 @@
#import "/src/utils.typ": *
#let offsets = (
(-1, 0),
(0, -1),
(1, 0),
(0, 1)
)
#let in-grid(w, h, x, y) = {
return 0 <= x and x < w and 0 <= y and y < h
}
#let solve(input) = {
let grid = input.split("\n").map(l => l.clusters())
let w = grid.first().len()
let h = grid.len()
let in-grid = in-grid.with(w, h)
let visited = ((false,) * w,) * h
let total = 0
for y in range(h) {
for x in range(w) {
if visited.at(y).at(x) {
continue
}
let char = grid.at(y).at(x)
let cells = ()
let borders = 0
let next-cells = ((x, y),)
while next-cells.len() != 0 {
let (cx, cy) = next-cells.remove(0)
cells.push((cx, cy))
visited.at(cy).at(cx) = true
for (dx, dy) in offsets {
let (x2, y2) = (cx + dx, cy + dy)
if (x2, y2) in cells or (x2, y2) in next-cells {
continue
}
if in-grid(x2, y2) {
let char2 = grid.at(y2).at(x2)
if char2 == char {
next-cells.push((x2, y2))
} else {
borders += 1
}
} else {
borders += 1
}
}
}
total += borders * cells.len()
}
}
return total
}
#show-puzzle(
12, 1,
solve,
example: (
"1": 140,
"2": 772,
"3": 1930
)
)

151
src/day12/puzzle2.typ Normal file
View File

@ -0,0 +1,151 @@
#import "/src/utils.typ": *
#let offsets = (
(-1, 0),
(0, -1),
(1, 0),
(0, 1)
)
#let in-grid(w, h, x, y) = {
return 0 <= x and x < w and 0 <= y and y < h
}
#let solve(input) = {
let grid = input.split("\n").map(l => l.clusters())
let w = grid.first().len()
let h = grid.len()
let in-grid = in-grid.with(w, h)
let zone-grid = ((none,) * w,) * h
let zone-id = 0
let zone-sides = ()
let zone-areas = ()
for y in range(h) {
for x in range(w) {
if zone-grid.at(y).at(x) != none {
continue
}
let char = grid.at(y).at(x)
let area = 0
let borders = 0
let next-cells = ((x, y),)
while next-cells.len() != 0 {
let (cx, cy) = next-cells.remove(0)
zone-grid.at(cy).at(cx) = zone-id
area += 1
for (dx, dy) in offsets {
let (x2, y2) = (cx + dx, cy + dy)
if (x2, y2) in next-cells {
continue
}
if in-grid(x2, y2) {
if zone-grid.at(y2).at(x2) == zone-id {
continue
}
let char2 = grid.at(y2).at(x2)
if char2 == char {
next-cells.push((x2, y2))
}
}
}
}
zone-areas.push(area)
zone-sides.push(0)
zone-id += 1
}
}
let first-zone0 = -1
let last-zone0 = -1
for y in range(h) {
let first-zone = zone-grid.at(y).at(0)
let last-zone = zone-grid.at(y).at(w - 1)
if first-zone0 != first-zone {
zone-sides.at(first-zone) += 1
}
if last-zone0 != last-zone {
zone-sides.at(last-zone) += 1
}
first-zone0 = first-zone
last-zone0 = last-zone
}
first-zone0 = -1
last-zone0 = -1
for x in range(w) {
let first-zone = zone-grid.at(0).at(x)
let last-zone = zone-grid.at(h - 1).at(x)
if first-zone0 != first-zone {
zone-sides.at(first-zone) += 1
}
if last-zone0 != last-zone {
zone-sides.at(last-zone) += 1
}
first-zone0 = first-zone
last-zone0 = last-zone
}
for x in range(w - 1) {
let zone-a0 = -1
let zone-b0 = -1
for y in range(h) {
let zone-a = zone-grid.at(y).at(x)
let zone-b = zone-grid.at(y).at(x + 1)
if zone-a != zone-b {
if zone-a != zone-a0 {
zone-sides.at(zone-a) += 1
}
if zone-b != zone-b0 {
zone-sides.at(zone-b) += 1
}
zone-a0 = zone-a
zone-b0 = zone-b
} else {
zone-a0 = -1
zone-b0 = -1
}
}
}
for y in range(h - 1) {
let zone-a0 = -1
let zone-b0 = -1
for x in range(w) {
let zone-a = zone-grid.at(y).at(x)
let zone-b = zone-grid.at(y + 1).at(x)
if zone-a != zone-b {
if zone-a != zone-a0 {
zone-sides.at(zone-a) += 1
}
if zone-b != zone-b0 {
zone-sides.at(zone-b) += 1
}
zone-a0 = zone-a
zone-b0 = zone-b
} else {
zone-a0 = -1
zone-b0 = -1
}
}
}
let total = range(zone-id).map(i => {
zone-sides.at(i) * zone-areas.at(i)
})
return total.sum()
}
#show-puzzle(
12, 2,
solve,
example: (
"1": 80,
"2": 436,
"3": 1206,
"4": 236,
"5": 368
)
)

86
src/day13/puzzle1.typ Normal file
View File

@ -0,0 +1,86 @@
#import "/src/utils.typ": *
#let parse-machine(lines) = {
let lines = lines.split("\n")
let match-a = lines.at(0).match(regex("Button A: X\\+(\d+), Y\\+(\d+)"))
let match-b = lines.at(1).match(regex("Button B: X\\+(\d+), Y\\+(\d+)"))
let match-p = lines.at(2).match(regex("Prize: X=(\d+), Y=(\d+)"))
return (
a: (
x: int(match-a.captures.first()),
y: int(match-a.captures.last()),
),
b: (
x: int(match-b.captures.first()),
y: int(match-b.captures.last()),
),
prize: (
x: int(match-p.captures.first()),
y: int(match-p.captures.last()),
)
)
}
#let in-line(v1, v2) = {
let f1 = v2.x / v1.x
let f2 = v2.y / v1.y
return f1 == f2
}
#let solve(input) = {
let machines = input.split("\n\n")
machines = machines.map(parse-machine)
let total = 0
for m in machines {
let totals = ()
let are-inline = in-line(m.a, m.b)
for b in range(101) {
let bx = b * m.b.x
let by = b * m.b.y
let rx = m.prize.x - bx
let ry = m.prize.y - by
if rx < 0 or ry < 0 {
break
}
let rax = calc.rem(
rx,
m.a.x
)
let ray = calc.rem(
ry,
m.a.y
)
if rax != 0 or ray != 0 {
continue
}
let a1 = calc.div-euclid(
rx,
m.a.x
)
let a2 = calc.div-euclid(
ry,
m.a.y
)
if a1 != a2 or a1 > 100 {
continue
}
totals.push(b + a1 * 3)
if not are-inline {
break
}
}
if totals.len() != 0 {
total += calc.min(..totals)
}
}
return total
}
#show-puzzle(
13, 1,
solve,
example: 480
)

74
src/day13/puzzle2.typ Normal file
View File

@ -0,0 +1,74 @@
#import "/src/utils.typ": *
#let parse-machine(lines) = {
let lines = lines.split("\n")
let match-a = lines.at(0).match(regex("Button A: X\\+(\d+), Y\\+(\d+)"))
let match-b = lines.at(1).match(regex("Button B: X\\+(\d+), Y\\+(\d+)"))
let match-p = lines.at(2).match(regex("Prize: X=(\d+), Y=(\d+)"))
return (
a: (
x: int(match-a.captures.first()),
y: int(match-a.captures.last()),
),
b: (
x: int(match-b.captures.first()),
y: int(match-b.captures.last()),
),
prize: (
x: int(match-p.captures.first()) + 10000000000000,
y: int(match-p.captures.last()) + 10000000000000,
)
)
}
#let mat-mul-v(mat, v) = {
return (
mat.at(0).at(0) * v.at(0) + mat.at(0).at(1) * v.at(1),
mat.at(1).at(0) * v.at(0) + mat.at(1).at(1) * v.at(1)
)
}
#let det(mat) = {
return mat.at(0).at(0) * mat.at(1).at(1) - mat.at(1).at(0) * mat.at(0).at(1)
}
#let solve(input) = {
let machines = input.split("\n\n")
machines = machines.map(parse-machine)
let total = 0
for m in machines {
let mat = (
(m.a.x, m.b.x),
(m.a.y, m.b.y)
)
let v = (m.prize.x, m.prize.y)
let mat2 = (
(mat.at(1).at(1), -mat.at(0).at(1)),
(-mat.at(1).at(0), mat.at(0).at(0)),
)
let (a, b) = mat-mul-v(mat2, v)
let d = det(mat)
// Check integer solution
if calc.rem(a, d) != 0 or calc.rem(b, d) != 0 {
continue
}
a = int(a / d)
b = int(b / d)
if a > 0 and b > 0 {
total += a * 3 + b
}
}
return total
}
#show-puzzle(
13, 2,
solve,
example: 875318608908
)

65
src/day14/puzzle1.typ Normal file
View File

@ -0,0 +1,65 @@
#import "/src/utils.typ": *
#let regexp = regex("^p=(.*?),(.*?) v=(.*?),(.*?)$")
#let simulate(v0, dv, max: 1, steps: 1) = {
return calc.rem-euclid(v0 + dv * steps, max)
}
#let parse-input(input) = {
return input.split("\n").map(b => {
let m = b.match(regexp)
return (
pos: (
x: int(m.captures.at(0)),
y: int(m.captures.at(1)),
),
vel: (
x: int(m.captures.at(2)),
y: int(m.captures.at(3))
)
)
})
}
#let solve(w: 0, h: 0, steps: 100, input) = {
assert(w != 0, message: "Width must be != 0")
assert(h != 0, message: "Height must be != 0")
let bots = parse-input(input)
let quadrants = (
tl: 0,
tr: 0,
bl: 0,
br: 0
)
let half-w = calc.div-euclid(w, 2)
let half-h = calc.div-euclid(h, 2)
let sim-x = simulate.with(max: w, steps: steps)
let sim-y = simulate.with(max: h, steps: steps)
for bot in bots {
let x2 = sim-x(bot.pos.x, bot.vel.x)
let y2 = sim-y(bot.pos.y, bot.vel.y)
if x2 == half-w or y2 == half-h {
continue
}
let quadrant = (
(if y2 < half-h {"t"} else {"b"}) +
(if x2 < half-w {"l"} else {"r"})
)
quadrants.at(quadrant) += 1
}
return quadrants.values().product()
}
#show-puzzle(
14, 1,
solve.with(w: 101, h: 103),
example: (
(result: 12, args: (w: 11, h: 7)),
)
)

33
src/day14/puzzle2.typ Normal file
View File

@ -0,0 +1,33 @@
#import "/src/utils.typ": *
#import "@preview/cetz:0.3.1": canvas, draw
#import "puzzle1.typ": parse-input, simulate
#let input = get-input(14)
#let bots = parse-input(input)
#let res = 8270
#let (width, height) = (101, 103)
#let sim-x = simulate.with(max: width, steps: res)
#let sim-y = simulate.with(max: height, steps: res)
#let size = 0.1
#figure(
canvas({
draw.rect(
(0, 0),
(width * size, -height * size)
)
for bot in bots {
let x = sim-x(bot.pos.x, bot.vel.x)
let y = sim-y(bot.pos.y, bot.vel.y)
draw.rect(
(x * size, -y * size),
((x + 1) * size, -(y + 1) * size),
stroke: none,
fill: black
)
}
}),
caption: "Christmas tree easter egg"
)
#show-result(res)

91
src/day15/puzzle1.typ Normal file
View File

@ -0,0 +1,91 @@
#import "/src/utils.typ": *
#let WALL = "#"
#let BOX = "O"
#let BOT = "@"
#let EMPTY = "."
#let offsets = (
"^": (0, -1),
"<": (-1, 0),
"v": (0, 1),
">": (1, 0)
)
#let compute-value(grid) = {
let total = 0
for (y, row) in grid.enumerate() {
for (x, type) in row.enumerate() {
if type == BOX {
total += y * 100 + x
}
}
}
return total
}
#let solve(input) = {
let (grid-data, move-data) = input.split("\n\n")
let grid = grid-data.split("\n").map(r => r.clusters())
let rows = ()
let cols = ()
let w = grid.first().len()
let h = grid.len()
let bot-pos = none
for y in range(h) {
for x in range(w) {
let type = grid.at(y).at(x)
if type == BOT {
bot-pos = (x, y)
grid.at(y).at(x) = EMPTY
break
}
}
if bot-pos != none {
break
}
}
let (bot-x, bot-y) = bot-pos
let moves = move-data.replace("\n", "").clusters()
for (move-i, move) in moves.enumerate() {
let (dx, dy) = offsets.at(move)
let (x2, y2) = (bot-x + dx, bot-y + dy)
let type = grid.at(y2).at(x2)
if type == WALL {
continue
}
if type == EMPTY {
(bot-x, bot-y) = (x2, y2)
continue
}
let type2 = type
let (x3, y3) = (x2, y2)
while type2 == BOX {
x3 += dx
y3 += dy
type2 = grid.at(y3).at(x3)
}
if type2 == WALL {
continue
}
grid.at(y3).at(x3) = BOX
grid.at(y2).at(x2) = EMPTY
(bot-x, bot-y) = (x2, y2)
}
return compute-value(grid)
}
#show-puzzle(
15, 1,
solve,
example: (
"1": 2028,
"2": 10092
)
)

123
src/day15/puzzle2.typ Normal file
View File

@ -0,0 +1,123 @@
#import "/src/utils.typ": *
#let WALL = "#"
#let BOX = "O"
#let BOX-L = "["
#let BOX-R = "]"
#let BOT = "@"
#let EMPTY = "."
#let offsets = (
"^": (0, -1),
"<": (-1, 0),
"v": (0, 1),
">": (1, 0)
)
#let compute-value(grid) = {
let total = 0
for (y, row) in grid.enumerate() {
for (x, type) in row.enumerate() {
if type == BOX-L {
total += y * 100 + x
}
}
}
return total
}
#let can-move(grid, w, h, x, y, dx, dy) = {
let (x2, y2) = (x + dx, y + dy)
let type = grid.at(y2).at(x2)
if type == EMPTY {
return true
}
if type == WALL {
return false
}
// Horizontal move
if dy == 0 {
return can-move(grid, w, h, x2, y2, dx, dy)
}
let lx = if type == BOX-L {x2} else {x2 - 1}
return (
can-move(grid, w, h, lx, y2, dx, dy) and
can-move(grid, w, h, lx + 1, y2, dx, dy)
)
}
#let do-move(grid, w, h, x, y, dx, dy) = {
let type = grid.at(y).at(x)
let (x2, y2) = (x + dx, y + dy)
let type2 = grid.at(y2).at(x2)
if type2 in (BOX-L, BOX-R) {
grid = do-move(grid, w, h, x2, y2, dx, dy)
// Vertical move
if dx == 0 {
let x3 = if type2 == BOX-L {x2 + 1} else {x2 - 1}
grid = do-move(grid, w, h, x3, y2, dx, dy)
}
type2 = EMPTY
}
if type2 == EMPTY {
grid.at(y2).at(x2) = type
grid.at(y).at(x) = EMPTY
return grid
}
if type2 == WALL {
panic()
}
return grid
}
#let solve(input) = {
let (grid-data, move-data) = input.split("\n\n")
let grid = grid-data.split("\n").map(r => r.clusters())
let w = grid.first().len()
let h = grid.len()
let (w2, h2) = (w * 2, h)
let bot-pos = none
let grid2 = ((EMPTY,) * w2,) * h2
for y in range(h) {
for x in range(w) {
let type = grid.at(y).at(x)
if type == BOT {
bot-pos = (x*2, y)
type = EMPTY
continue
}
grid2.at(y).at(x*2) = if type == BOX {BOX-L} else {type}
grid2.at(y).at(x*2 + 1) = if type == BOX {BOX-R} else {type}
}
}
let (bot-x, bot-y) = bot-pos
let moves = move-data.replace("\n", "").clusters()
for (move-i, move) in moves.enumerate() {
let (dx, dy) = offsets.at(move)
let (x2, y2) = (bot-x + dx, bot-y + dy)
if not can-move(grid2, w2, h2, bot-x, bot-y, dx, dy) {
continue
}
grid2 = do-move(grid2, w2, h2, bot-x, bot-y, dx, dy)
(bot-x, bot-y) = (x2, y2)
}
return compute-value(grid2)
}
#show-puzzle(
15, 2,
solve,
example: (
"2": 9021
)
)

67
src/day16/puzzle1.typ Normal file
View File

@ -0,0 +1,67 @@
#import "/src/utils.typ": *
#let START = "S"
#let END = "E"
#let WALL = "#"
#let EMPTY = "."
#let offsets = (
(1, 0),
(0, 1),
(-1, 0),
(0, -1)
)
#let solve(input) = {
let grid = input.split("\n").map(l => l.clusters())
let w = grid.first().len()
let h = grid.len()
let (sx, sy) = (0, 0)
let (ex, ey) = (0, 0)
for y in range(h) {
for x in range(w) {
let c = grid.at(y).at(x)
if c == START {
(sx, sy) = (x, y)
} else if c == END {
(ex, ey) = (x, y)
}
}
}
let choices = ((sx, sy, 0, 0),)
let (x, y, dir, score) = (0, 0, 0, 0)
while choices.len() != 0 {
let min-score = calc.min(..choices.map(c => c.last()))
let i = choices.position(c => c.last() == min-score)
(x, y, dir, score) = choices.remove(i)
for (d, (dx, dy)) in offsets.enumerate() {
// Ignore backflips
if calc.abs(d - dir) == 2 {
continue
}
let (x2, y2) = (x + dx, y + dy)
let c = grid.at(y2).at(x2)
if c == WALL {
continue
}
let score2 = score + 1 + if d != dir {1000} else {0}
if c == END {
return score2
}
choices.push((x2, y2, d, score2))
}
break
}
return 0
}
#show-puzzle(
16, 1,
solve,
example: (
"1": 7036,
"2": 11048
)
)

0
src/day16/puzzle2.typ Normal file
View File

87
src/day17/puzzle1.typ Normal file
View File

@ -0,0 +1,87 @@
#import "/src/utils.typ": *
#let ADV = 0
#let BXL = 1
#let BST = 2
#let JNZ = 3
#let BXC = 4
#let OUT = 5
#let BDV = 6
#let CDV = 7
#let get-combo(regs, value) = {
if value >= 7 {
panic()
}
if value <= 3 {
return value
}
return regs.at(value - 4)
}
#let solve(input) = {
let (registers, program) = input.split("\n\n")
let regs = ()
for line in registers.split("\n") {
regs.push(
int(
line.split(": ")
.last()
)
)
}
program = program.split(": ")
.last()
.split(",")
.map(int)
let out = ()
let pc = 0
while pc < program.len() {
let op = program.at(pc)
let val = program.at(pc + 1)
if op == ADV {
let num = regs.at(0)
let den = get-combo(regs, val)
let res = num.bit-rshift(den)
regs.at(0) = res
} else if op == BXL {
regs.at(1) = regs.at(1).bit-xor(val)
} else if op == BST {
regs.at(1) = get-combo(regs, val).bit-and(0b111)
} else if op == JNZ {
if regs.at(0) != 0 {
pc = val
continue
}
} else if op == BXC {
regs.at(1) = regs.at(1).bit-xor(regs.at(2))
} else if op == OUT {
out.push(get-combo(regs, val).bit-and(0b111))
} else if op == BDV {
let num = regs.at(0)
let den = get-combo(regs, val)
let res = num.bit-rshift(den)
regs.at(1) = res
} else if op == CDV {
let num = regs.at(0)
let den = get-combo(regs, val)
let res = num.bit-rshift(den)
regs.at(2) = res
} else {
panic("Unknown instruction " + str(op))
}
pc += 2
}
return out.map(str).join(",")
}
#show-puzzle(
17, 1,
solve,
example: (
"1": "4,6,3,5,6,3,5,2,1,0"
)
)

108
src/day17/puzzle2.typ Normal file
View File

@ -0,0 +1,108 @@
#import "/src/utils.typ": *
#let ADV = 0
#let BXL = 1
#let BST = 2
#let JNZ = 3
#let BXC = 4
#let OUT = 5
#let BDV = 6
#let CDV = 7
#let ops = ("ADV", "BXL", "BST", "JNZ", "BXC", "OUT", "BDV", "CDV")
#let get-combo(regs, value) = {
if value >= 7 {
panic()
}
if value <= 3 {
return value
}
return regs.at(value - 4)
}
#let describe-combo(value) = {
if value == 7 {
return "<invalid>"
}
if value <= 3 {
return str(value)
}
return "ABC".at(value - 4)
}
#let describe(program) = {
let res = ""
res += program.map(str).join(",")
for i in range(0, program.len(), step: 2) {
res += "\n"
let op = program.at(i)
let val = program.at(i + 1)
let combo = describe-combo(val)
res += ops.at(op) + ": "
if op == ADV {
res += "A >>= " + combo
} else if op == BXL {
res += "B ^= " + str(val)
} else if op == BST {
res += "B = " + combo + " & 0b111"
} else if op == JNZ {
res += "IF A != 0 {PC = " + str(val) + "}"
} else if op == BXC {
res += "B ^= C"
} else if op == OUT {
res += "OUT(" + combo + " & 0b111)"
} else if op == BDV {
res += "B = A >> " + combo
} else if op == CDV {
res += "C = A >> " + combo
}
}
return res
}
#let solve(input) = {
let (registers, program) = input.split("\n\n")
let regs = ()
for line in registers.split("\n") {
regs.push(
int(
line.split(": ")
.last()
)
)
}
program = program.split(": ")
.last()
.split(",")
.map(int)
let out = ()
let pc = 0
regs.first() = 0
for n in program {
let b = n
}
return raw(block: true, describe(program))
}
Example:
#solve(read(get-example-path(17, suffix: "2")))
Input:
#solve(get-input(17))
/*
#show-puzzle(
17, 2,
solve,
example: (
"2": 117440
)
)*/

143
src/day18/puzzle1.typ Normal file
View File

@ -0,0 +1,143 @@
#import "/src/utils.typ": *
#let offsets = (
(1, 0),
(0, 1),
(-1, 0),
(0, -1)
)
#let get-lowest(nodes, scores) = {
let lowest-score = none
let lowest-i = none
for (i, (x, y)) in nodes.enumerate() {
let score = scores.at(y).at(x)
if lowest-i == none or score < lowest-score {
lowest-score = score
lowest-i = i
}
}
if lowest-i == none {
panic()
}
return lowest-i
}
#let make-path(parents, end) = {
let path = (end,)
let (x, y) = end
let pos = parents.at(y).at(x)
while pos != none {
(x, y) = pos
path.insert(0, pos)
pos = parents.at(y).at(x)
}
return path
}
#let find-path(w, h, grid) = {
let end = (w - 1, h - 1)
let (x, y) = (0, 0)
let open = ((x, y),)
let closed = ()
let g-scores = ((0,) * w,) * h
let f-scores = ((calc.inf,) * w,) * h
let parents = ((none,)*w,)*h
while open.len() != 0 {
let cur = open.remove(get-lowest(open, f-scores))
if cur == end {
return make-path(parents, cur)
}
let g-score = g-scores.at(y).at(x)
let f-score = f-scores.at(y).at(x)
let (x, y) = cur
for (dx, dy) in offsets {
let (x2, y2) = (x + dx, y + dy)
if x2 < 0 or x2 >= w or y2 < 0 or y2 >= h {
continue
}
if grid.at(y2).at(x2) {
continue
}
if (x2, y2) in closed {
continue
}
let g = x2 + y2
let h = calc.abs(end.first() - x2) + calc.abs(end.last() - y2)
let f = g + h
if f < f-scores.at(y2).at(x2) {
g-scores.at(y2).at(x2) = g
f-scores.at(y2).at(x2) = f
parents.at(y2).at(x2) = (x, y)
open.push((x2, y2))
}
}
closed.push(cur)
}
panic("No path was found")
}
#let solve(input, w: 0, h: 0, n-bytes: 0) = {
assert(w != 0, message: "Width cannot be 0")
assert(h != 0, message: "Height cannot be 0")
let grid = ((false,) * w,) * h
let obstacles = input.split("\n")
for obs in obstacles.slice(0, n-bytes) {
let (x, y) = obs.split(",").map(int)
grid.at(y).at(x) = true
}
let path = find-path(w, h, grid)
return path.len() - 1
}
#let visualize(input, w: 0, h: 0, n-bytes: 0, s: 2em) = {
let grid_ = ((false,) * w,) * h
let obstacles = input.split("\n")
for obs in obstacles.slice(0, n-bytes) {
let (x, y) = obs.split(",").map(int)
grid_.at(y).at(x) = true
}
let path = find-path(w, h, grid_)
for (x, y) in path {
grid_.at(y).at(x) = grid.cell(fill: green.lighten(60%))[O]
}
let cells = grid_.flatten().map(c => {
if c == false []
else if c == true {grid.cell(fill: red.lighten(60%))[\#]}
else {c}
})
grid(
columns: (s,) * w,
rows: (s,) * h,
align: center + horizon,
stroke: black,
..cells
)
}
#show-puzzle(
18, 1,
solve.with(w: 71, h: 71, n-bytes: 1024),
example: (
(result: 22, args: (w: 7, h: 7, n-bytes: 12)),
),
visualize: visualize.with(w: 7, h: 7, n-bytes: 12)
)
/*
#pagebreak()
#set page(width: auto, height: auto)
#visualize(get-input(18), w: 71, h: 71, n-bytes: 1024, s: 1em)
*/

117
src/day18/puzzle2.typ Normal file
View File

@ -0,0 +1,117 @@
#import "/src/utils.typ": *
#let offsets = (
(1, 0),
(0, 1),
(-1, 0),
(0, -1)
)
#let get-lowest(nodes, scores) = {
let lowest-score = none
let lowest-i = none
for (i, (x, y)) in nodes.enumerate() {
let score = scores.at(y).at(x)
if lowest-i == none or score < lowest-score {
lowest-score = score
lowest-i = i
}
}
if lowest-i == none {
panic()
}
return lowest-i
}
#let make-path(parents, end) = {
let path = (end,)
let (x, y) = end
let pos = parents.at(y).at(x)
while pos != none {
(x, y) = pos
path.insert(0, pos)
pos = parents.at(y).at(x)
}
return path
}
#let find-path(w, h, grid, start: (0, 0), end: auto) = {
let end = if end == auto {(w - 1, h - 1)} else {end}
let (x, y) = start
let open = ((x, y),)
let closed = ()
let g-scores = ((0,) * w,) * h
let f-scores = ((calc.inf,) * w,) * h
let parents = ((none,)*w,)*h
while open.len() != 0 {
let cur = open.remove(get-lowest(open, f-scores))
if cur == end {
return true
}
let g-score = g-scores.at(y).at(x)
let f-score = f-scores.at(y).at(x)
let (x, y) = cur
for (dx, dy) in offsets {
let (x2, y2) = (x + dx, y + dy)
if x2 < 0 or x2 >= w or y2 < 0 or y2 >= h {
continue
}
if grid.at(y2).at(x2) {
continue
}
if (x2, y2) in closed {
continue
}
let g = calc.abs(x2 - start.first()) + calc.abs(y2 - start.last())
let h = calc.abs(end.first() - x2) + calc.abs(end.last() - y2)
let f = g + h
if f < f-scores.at(y2).at(x2) {
g-scores.at(y2).at(x2) = g
f-scores.at(y2).at(x2) = f
parents.at(y2).at(x2) = (x, y)
open.push((x2, y2))
}
}
closed.push(cur)
}
return false
}
#let solve(input, w: 0, h: 0, n-bytes: 0) = {
assert(w != 0, message: "Width cannot be 0")
assert(h != 0, message: "Height cannot be 0")
let grid = ((false,) * w,) * h
let obstacles = input.split("\n")
let a = 0
let b = obstacles.len()
while b - a > 1 {
let m = calc.div-euclid(a + b, 2)
let grid2 = grid
for obs in obstacles.slice(0, m) {
let (x, y) = obs.split(",").map(int)
grid2.at(y).at(x) = true
}
if find-path(w, h, grid2) {
a = m
} else {
b = m
}
}
return obstacles.at(a)
}
#show-puzzle(
18, 2,
solve.with(w: 71, h: 71),
example: (
(result: "6,1", args: (w: 7, h: 7)),
)
)

41
src/day19/puzzle1.typ Normal file
View File

@ -0,0 +1,41 @@
#import "/src/utils.typ": *
#let is-possible(target, towels: none) = {
if towels == none {
panic()
}
for towel in towels {
if towel == target {
return true
}
if target.starts-with(towel) {
if is-possible(target.slice(towel.len()), towels: towels) {
return true
}
}
}
return false
}
#let solve(input) = {
let (towels, targets) = input.split("\n\n")
towels = towels.split(", ")
let is-possible = is-possible.with(towels: towels)
let total = 0
for target in targets.split("\n") {
if is-possible(target) {
total += 1
}
}
return total
}
#show-puzzle(
19, 1,
solve,
example: 6
)

46
src/day19/puzzle2.typ Normal file
View File

@ -0,0 +1,46 @@
#import "/src/utils.typ": *
#let solve(input) = {
let (towels, targets) = input.split("\n\n")
towels = towels.split(", ")
let by-initial = (:)
for towel in towels {
let initial = towel.first()
if initial not in by-initial {
by-initial.insert(initial, ())
}
by-initial.at(initial).push(towel)
}
let count(target) = {
let initial = target.first()
if initial not in by-initial {
return 0
}
let cnt = 0
for towel in by-initial.at(initial) {
if towel == target {
cnt += 1
} else if target.starts-with(towel) {
cnt += count(target.slice(towel.len()))
}
}
return cnt
}
let total = 0
for target in targets.split("\n") {
total += count(target)
}
return total
}
#show-puzzle(
19, 2,
solve,
example: 16,
only-example: true
)
#show-result(632423618484345)

128
src/day20/puzzle1.typ Normal file
View File

@ -0,0 +1,128 @@
#import "/src/utils.typ": *
#let offsets = (
(1, 0),
(0, 1),
(-1, 0),
(0, -1)
)
#let EMPTY = "."
#let WALL = "#"
#let START = "S"
#let END = "E"
#let in-grid(w, h, x, y) = {
return 0 <= x and x < w and 0 <= y and y < h
}
#let count-shortcuts(grid, w, h, save, path) = {
let in-grid = in-grid.with(w, h)
let total = 0
for (x1, y1) in path {
let v1 = grid.at(y1).at(x1)
for (dx1, dy1) in offsets {
let (x2, y2) = (x1 + dx1, y1 + dy1)
if not in-grid(x2, y2) {
continue
}
let v2 = grid.at(y2).at(x2)
if v2 < 0 {
for (dx2, dy2) in offsets {
let (x3, y3) = (x2 + dx2, y2 + dy2)
if not in-grid(x3, y3) {
continue
}
let v3 = grid.at(y3).at(x3)
if v3 > 0 {
if v3 - v1 >= save + 2 {
total += 1
}
}
}
}
}
}
return total
}
#let solve(input, save: 1) = {
let input-grid = input.split("\n").map(l => l.clusters())
let w = input-grid.first().len()
let h = input-grid.len()
let in-grid = in-grid.with(w, h)
let grid = ((-1,)*w,)*h
let start = none
let end = none
for y in range(h) {
for x in range(w) {
let c = input-grid.at(y).at(x)
if c == WALL {
continue
}
if c == START {
start = (x, y)
} else if c == END {
end = (x, y)
}
grid.at(y).at(x) = 0
}
}
let (x, y) = start
let (ex, ey) = end
let path = ()
while x != ex or y != ey {
path.push((x, y))
grid.at(y).at(x) = path.len()
for (dx, dy) in offsets {
let (x2, y2) = (x + dx, y + dy)
if not in-grid(x2, y2) {
continue
}
let v = grid.at(y2).at(x2)
if v == 0 {
(x, y) = (x2, y2)
break
}
}
}
path.push((x, y))
grid.at(y).at(x) = path.len()
return count-shortcuts(
grid, w, h,
save,
path
)
}
#let examples = (
(64, 1),
(40, 1),
(38, 1),
(36, 1),
(20, 1),
(12, 3),
(10, 2),
(8, 4),
(6, 2),
(4, 14),
(2, 14),
)
#let n-tot = 0
#let examples2 = ()
#for (save, cnt) in examples {
n-tot += cnt
examples2.push((result: n-tot, args: (save: save)))
}
#show-puzzle(
20, 1,
solve.with(save: 100),
example: examples2
)

0
src/day20/puzzle2.typ Normal file
View File

100
src/day21/puzzle1.typ Normal file
View File

@ -0,0 +1,100 @@
#import "/src/utils.typ": *
#let nums = (
"7": (0, 0),
"8": (1, 0),
"9": (2, 0),
"4": (0, 1),
"5": (1, 1),
"6": (2, 1),
"1": (0, 2),
"2": (1, 2),
"3": (2, 2),
"0": (1, 3),
"A": (2, 3),
)
#let arrows = (
"^": (1, 0),
"A": (2, 0),
"<": (0, 1),
"v": (1, 1),
">": (2, 1),
)
#let get-num-path(code) = {
let path = ""
let (x, y) = nums.at("A")
for char in code {
let (x2, y2) = nums.at(char)
let dy = y2 - y
let dx = x2 - x
let ver = if dy == 0 {
""
} else {
(if dy < 0 {"^"} else {"v"}) * calc.abs(dy)
}
let hor = if dx == 0 {
""
} else {
(if dx < 0 {"<"} else {">"}) * calc.abs(dx)
}
path += if y == 3 and x2 == 0 {
ver + hor
} else {
hor + ver
} + "A"
(x, y) = (x2, y2)
}
return path
}
#let get-dir-path(code) = {
let path = ""
let (x, y) = arrows.at("A")
for char in code {
let (x2, y2) = arrows.at(char)
let dy = y2 - y
let dx = x2 - x
let ver = if dy == 0 {
""
} else {
(if dy < 0 {"^"} else {"v"}) * calc.abs(dy)
}
let hor = if dx == 0 {
""
} else {
(if dx < 0 {"<"} else {">"}) * calc.abs(dx)
}
path += if x == 0 and dy < 0 {
hor + ver
} else {
ver + hor
} + "A"
(x, y) = (x2, y2)
}
return path
}
#let get-path(code) = {
let num-path = get-num-path(code)
let dir-path-1 = get-dir-path(num-path)
let dir-path-2 = get-dir-path(dir-path-1)
return dir-path-2
}
#let solve(input) = {
let codes = input.split("\n")
let total = 0
for code in codes {
let len = get-path(code).len()
let num = int(code.slice(0, code.len() - 1))
total += len * num
}
return total
}
#show-puzzle(
21, 1,
solve,
example: 126384
)

0
src/day21/puzzle2.typ Normal file
View File

61
src/day22/puzzle1.typ Normal file
View File

@ -0,0 +1,61 @@
#import "/src/utils.typ": *
#let random(prev) = {
let step(a, shift) = {
let s = if shift < 0 {
a.bit-rshift(-shift)
} else {
a.bit-lshift(shift)
}
return s.bit-xor(a).bit-and(0xffffff)
}
let b = step(prev, 6)
let c = step(b, -5)
let d = step(c, 11)
return d
}
#let solve(input, steps: 1) = {
let values = input.split("\n").map(int)
let total = 0
for value in values {
for _ in range(steps) {
value = random(value)
}
total += value
}
return total
}
#let examples = ()
#let results = (
15887950,
16495136,
527345,
704524,
1553684,
12683156,
11100544,
12249484,
7753432,
5908254
)
#for (i, res) in results.enumerate() {
examples.push((
result: res,
args: (steps: i + 1)
))
}
#show-puzzle(
22, 1,
solve.with(steps: 2000),
example: examples,
only-example: true
)
#show-result(18261820068)
//B = ((A << 6) ^ A) & 0xffffff\
//C = ((B >> 5) ^ B) & 0xffffff\
//D = ((C << 11) ^ C) & 0xffffff

0
src/day22/puzzle2.typ Normal file
View File

50
src/day23/puzzle1.typ Normal file
View File

@ -0,0 +1,50 @@
#import "/src/utils.typ": *
#let solve(input) = {
let links = input.split("\n")
let links-dict = (:)
let to-test = ()
for link in links {
let (a, b) = link.split("-")
if a not in links-dict {
links-dict.insert(a, ())
}
if b not in links-dict {
links-dict.insert(b, ())
}
links-dict.at(a).push(b)
links-dict.at(b).push(a)
if a.starts-with("t") {
to-test.push(a)
}
if b.starts-with("t") {
to-test.push(b)
}
}
let total = 0
let groups = ()
for comp1 in to-test.dedup() {
for comp2 in links-dict.at(comp1) {
for comp3 in links-dict.at(comp2) {
if comp1 in links-dict.at(comp3) {
let group = (comp1, comp2, comp3).sorted()
if group not in groups {
total += 1
groups.push(group)
}
}
}
}
}
return total
}
#show-puzzle(
23, 1,
solve,
example: 7
)

104
src/day23/puzzle2.typ Normal file
View File

@ -0,0 +1,104 @@
#import "/src/utils.typ": *
#let bron-kerbosch(links, R, P, X) = {
if P.len() == 0 and X.len() == 0 {
return if R.len() > 2 {
R.sorted()
} else {
none
}
}
let longest-len = 0
let longest = none
let to-visit = P
for v in to-visit {
let neighbors = links.at(v)
let clique = bron-kerbosch(
links,
R + (v,),
P.filter(n => n in neighbors),
X.filter(n => n in neighbors)
)
if clique != none {
let l = clique.len()
if longest == none or l > longest-len {
longest = clique
longest-len = l
}
}
let _ = P.remove(0)
X.push(v)
}
return longest
}
#let bron-kerbosch2(links, R, P, X) = {
if P.len() == 0 and X.len() == 0 {
return if R.len() > 2 {
R.sorted()
} else {
none
}
}
let pivot = if P.len() != 0 {
P.first()
} else {
X.first()
}
let pivot-neighbors = links.at(pivot)
let longest-len = 0
let longest = none
let to-visit = P.filter(n => n not in pivot-neighbors)
for v in to-visit {
let neighbors = links.at(v)
let clique = bron-kerbosch2(
links,
R + (v,),
P.filter(n => n in neighbors),
X.filter(n => n in neighbors)
)
if clique != none {
let l = clique.len()
if longest == none or l > longest-len {
longest = clique
longest-len = l
}
}
let _ = P.remove(P.position(n => n == v))
X.push(v)
}
return longest
}
#let solve(input) = {
let links = input.split("\n")
let links-dict = (:)
let to-test = ()
for link in links {
let (a, b) = link.split("-")
if a not in links-dict {
links-dict.insert(a, ())
}
if b not in links-dict {
links-dict.insert(b, ())
}
links-dict.at(a).push(b)
links-dict.at(b).push(a)
}
let clique = bron-kerbosch2(links-dict, (), links-dict.keys(), ())
return clique.join(",")
}
#show-puzzle(
23, 2,
solve,
example: "co,de,ka,ta",
only-example: true
)
#show-result("ab,al,cq,cr,da,db,dr,fw,ly,mn,od,py,uh")

78
src/day24/puzzle1.typ Normal file
View File

@ -0,0 +1,78 @@
#import "/src/utils.typ": *
#let solve(input) = {
let (inputs, gates) = input.split("\n\n")
let ids = ()
inputs = inputs.split("\n").map(i => {
let (id, value) = i.split(": ")
return (id: id, value: value == "1")
})
ids += inputs.map(i => i.id)
gates = gates.split("\n").map(g => {
let (gate, output) = g.split(" -> ")
let (i1, op, i2) = gate.split(" ")
return (
in1: i1,
in2: i2,
op: op,
out: output
)
})
let gates-by-id = (:)
for gate in gates {
gates-by-id.insert(gate.out, gate)
}
ids += gates.map(g => g.out)
ids = ids.dedup()
let dbg = (inputs, gates)
let values = (:)
for input in inputs {
values.insert(input.id, input.value)
}
let stack = ids.filter(id => id.starts-with("z"))
let output = (0,) * stack.len()
while stack.len() != 0 {
let v = stack.pop()
if v in values {
if v.starts-with("z") {
let i = int(v.slice(1))
output.at(i) = int(values.at(v))
}
} else {
stack.push(v)
let gate = gates-by-id.at(v)
if gate.in1 in values and gate.in2 in values {
let v1 = values.at(gate.in1)
let v2 = values.at(gate.in2)
let value = if gate.op == "AND" {
v1 and v2
} else if gate.op == "OR" {
v1 or v2
} else if gate.op == "XOR" {
(v1 or v2) and not (v1 and v2)
}
values.insert(v, value)
} else {
stack.push(gate.in1)
stack.push(gate.in2)
}
}
}
let result = output.rev()
.fold(0, (a, b) => a.bit-lshift(1).bit-or(b))
return result
}
#show-puzzle(
24, 1,
solve,
example: (
"1": 4,
"2": 2024
)
)

0
src/day24/puzzle2.typ Normal file
View File

61
src/day25/puzzle1.typ Normal file
View File

@ -0,0 +1,61 @@
#import "/src/utils.typ": *
#let parse-schematic(schematic) = {
let lines = schematic.split("\n")
let is-key = "." in schematic.first()
let n-cols = lines.first().len()
let n-rows = lines.len()
let heights = ()
for x in range(n-cols) {
let y = if is-key {n-rows - 2} else {1}
let h = 0
for i in range(n-rows) {
if lines.at(y).at(x) == "." {
break
}
h += 1
y += if is-key {-1} else {1}
}
heights.push(h)
}
return (if is-key {"key"} else {"lock"}, heights)
}
#let fits(lock, key) = {
let tmp = lock.zip(key).map(p => p.sum())
return calc.max(..tmp) <= 5
}
#let solve(input) = {
let schematics = input.split("\n\n")
let locks = ()
let keys = ()
for schematic in schematics {
let (type, heights) = parse-schematic(schematic)
if type == "key" {
keys.push(heights)
} else {
locks.push(heights)
}
}
let total = 0
for key in keys {
for lock in locks {
if fits(lock, key) {
total += 1
}
}
}
return total
}
#show-puzzle(
25, 1,
solve,
example: 3
)

0
src/day25/puzzle2.typ Normal file
View File

Binary file not shown.

View File

@ -81,6 +81,12 @@
for (suffix, result) in example.pairs() {
check-example(day, func, result, suffix: suffix)
}
} else if type(example) == array {
for ex in example {
let suffix = ex.at("suffix", default: none)
let args = ex.at("args", default: (:))
check-example(day, func.with(..args), ex.result, suffix: suffix)
}
} else {
check-example(day, func, example)
}
@ -191,7 +197,9 @@
set text(font: "Source Sans 3")
set page(
footer: context {
align(center, counter(page).display("1 / 1", both: true))
if counter(page).get().first() != 1 {
align(center, counter(page).display("1 / 1", both: true))
}
}
)
body