mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Compare commits
55 Commits
b23f24c567
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bc6f3dc8c6 | |||
| a8a1c85498 | |||
| ff8b8dac0c | |||
| 51ca5db42e | |||
| e60bfbf8c4 | |||
| 768a3ff10b | |||
| c3d3116f8a | |||
| 13592a60ee | |||
| be6c3d37ea | |||
| ff2c86a11b | |||
| a737a35227 | |||
| 29e02c819e | |||
| a01eb547d1 | |||
| 1485eb3cd5 | |||
| 0a7ec71a97 | |||
| f2204e554b | |||
| 6f4b02af33 | |||
| 7e23cf94a6 | |||
| f132842b5c | |||
| 027a7bdde6 | |||
| 2277721010 | |||
| 5e9a24c8d7 | |||
| 1468c87347 | |||
| 1dc59c18eb | |||
| dbcbd15103 | |||
| b6aafa1b27 | |||
| 1f7108be47 | |||
| 48fb8cf8c9 | |||
| f9416db251 | |||
| 6941f2b2d2 | |||
| dc92b65830 | |||
| 5c030d5272 | |||
| d07bb9235b | |||
| b23676bf04 | |||
| 40632c8114 | |||
| 073b576fd8 | |||
| f3a3e1fca3 | |||
| 4a479c1646 | |||
| b41571949e | |||
| e949ce9932 | |||
| 48824288b0 | |||
| 9e552c9b6a | |||
| 329cd4e471 | |||
| 321d78d850 | |||
| 230b4ae028 | |||
| be2244eca9 | |||
| 395463dc4a | |||
| fdaadfe184 | |||
| dd07090056 | |||
| 891a5ea50c | |||
| d4aad95f55 | |||
| caa2c9b6cf | |||
| 4a8b9f9109 | |||
| 6c0a49a5f3 | |||
| 17017e7ab4 |
43
.github/workflows/2024.yml
vendored
43
.github/workflows/2024.yml
vendored
@@ -1,43 +0,0 @@
|
|||||||
on:
|
|
||||||
- push
|
|
||||||
|
|
||||||
name: Advent of Code 2024
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version:
|
|
||||||
- "3.12"
|
|
||||||
- "3.13"
|
|
||||||
|
|
||||||
name: Continuous Integration
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v3
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: "2024/uv.lock"
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
working-directory: "2024"
|
|
||||||
run: >
|
|
||||||
uv run ruff format --check
|
|
||||||
|
|
||||||
- name: Run lints
|
|
||||||
working-directory: "2024"
|
|
||||||
run: >
|
|
||||||
uv run ruff check
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
working-directory: "2024"
|
|
||||||
run: >
|
|
||||||
uv run pytest tests
|
|
||||||
15
2024/bonus/24todot.py
Normal file
15
2024/bonus/24todot.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import fileinput
|
||||||
|
|
||||||
|
print("digraph day24 {")
|
||||||
|
|
||||||
|
for line in fileinput.input():
|
||||||
|
parts = line.split(" ")
|
||||||
|
if len(parts) != 5:
|
||||||
|
continue
|
||||||
|
|
||||||
|
first, op, second, _, result = parts
|
||||||
|
print(f'{first}{second}{op} [label="{op}"];')
|
||||||
|
print(f"{first} -> {first}{second}{op} -> {result};")
|
||||||
|
print(f"{second} -> {first}{second}{op};")
|
||||||
|
|
||||||
|
print("}")
|
||||||
@@ -6,8 +6,8 @@ locals {
|
|||||||
cleaned_input = replace(var.input, "/ +/", " ")
|
cleaned_input = replace(var.input, "/ +/", " ")
|
||||||
lines = split("\n", trim(local.cleaned_input, "\n"))
|
lines = split("\n", trim(local.cleaned_input, "\n"))
|
||||||
lines_split = [for line in local.lines : split(" ", line)]
|
lines_split = [for line in local.lines : split(" ", line)]
|
||||||
left = [for line in local.lines_split : parseint(line[0], 10)]
|
left = [for line in local.lines_split : tonumber(line[0])]
|
||||||
right = [for line in local.lines_split : parseint(line[1], 10)]
|
right = [for line in local.lines_split : tonumber(line[1])]
|
||||||
|
|
||||||
left_sorted = sort(local.left)
|
left_sorted = sort(local.left)
|
||||||
right_sorted = sort(local.right)
|
right_sorted = sort(local.right)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ variable "input" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
reports = [for line in split("\n", trim(var.input, "\n")) : [for num in split(" ", line) : parseint(num, 10)]]
|
reports = [for line in split("\n", trim(var.input, "\n")) : split(" ", line)]
|
||||||
}
|
}
|
||||||
|
|
||||||
module "part1_valid" {
|
module "part1_valid" {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
variable "update" {
|
|
||||||
type = list(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "disallow_rules" {
|
|
||||||
type = map(list(number))
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
not_disallowed = alltrue([
|
|
||||||
for i in range(1, length(var.update)) :
|
|
||||||
!contains(
|
|
||||||
flatten([for j in range(i) : lookup(var.disallow_rules, var.update[j], [])]),
|
|
||||||
var.update[i]
|
|
||||||
)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "valid" {
|
|
||||||
value = local.not_disallowed ? var.update[floor(length(var.update) / 2)] : 0
|
|
||||||
}
|
|
||||||
@@ -8,16 +8,18 @@ locals {
|
|||||||
disallow_rules = { for rule in local.rules : rule[1] => rule[0]... }
|
disallow_rules = { for rule in local.rules : rule[1] => rule[0]... }
|
||||||
|
|
||||||
updates = [for update_line in split("\n", local.parts[1]) : [for v in split(",", update_line) : tonumber(v)]]
|
updates = [for update_line in split("\n", local.parts[1]) : [for v in split(",", update_line) : tonumber(v)]]
|
||||||
}
|
|
||||||
|
|
||||||
module "is_valid" {
|
scores = [
|
||||||
source = "./is_correct"
|
for update in local.updates :
|
||||||
count = length(local.updates)
|
alltrue([
|
||||||
|
for i in range(1, length(update)) :
|
||||||
update = local.updates[count.index]
|
!contains(
|
||||||
disallow_rules = local.disallow_rules
|
flatten([for j in range(i) : lookup(local.disallow_rules, update[j], [])]),
|
||||||
|
update[i]
|
||||||
|
)
|
||||||
|
]) ? update[floor(length(update) / 2)] : 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "part1" {
|
output "part1" {
|
||||||
value = sum(module.is_valid[*].valid)
|
value = sum(local.scores[*])
|
||||||
}
|
}
|
||||||
|
|||||||
52
2024/bonus/day08/freq/main.tf
Normal file
52
2024/bonus/day08/freq/main.tf
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
variable "width" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "height" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "antennae" {
|
||||||
|
type = list(tuple([number, number]))
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
pairs = concat([
|
||||||
|
for i in range(length(var.antennae)) :
|
||||||
|
[
|
||||||
|
for j in range(i + 1, length(var.antennae)) : [var.antennae[i], var.antennae[j]]
|
||||||
|
]
|
||||||
|
]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
module "pair" {
|
||||||
|
source = "./pair"
|
||||||
|
count = length(local.pairs)
|
||||||
|
|
||||||
|
first = local.pairs[count.index][0]
|
||||||
|
second = local.pairs[count.index][1]
|
||||||
|
width = var.width
|
||||||
|
height = var.height
|
||||||
|
}
|
||||||
|
|
||||||
|
output "nodes1" {
|
||||||
|
value = setunion([
|
||||||
|
for i in range(length(local.pairs)) :
|
||||||
|
[
|
||||||
|
for v in module.pair[i].nodes1 :
|
||||||
|
v
|
||||||
|
if v[0] >= 0 && v[0] < var.width && v[1] >= 0 && v[1] < var.height
|
||||||
|
]
|
||||||
|
]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
output "nodes2" {
|
||||||
|
value = setunion([
|
||||||
|
for i in range(length(local.pairs)) :
|
||||||
|
[
|
||||||
|
for v in module.pair[i].nodes2 :
|
||||||
|
v
|
||||||
|
if v[0] >= 0 && v[0] < var.width && v[1] >= 0 && v[1] < var.height
|
||||||
|
]
|
||||||
|
]...)
|
||||||
|
}
|
||||||
52
2024/bonus/day08/freq/pair/main.tf
Normal file
52
2024/bonus/day08/freq/pair/main.tf
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
variable "first" {
|
||||||
|
type = tuple([number, number])
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "second" {
|
||||||
|
type = tuple([number, number])
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "width" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "height" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
dx = var.second[0] - var.first[0]
|
||||||
|
dy = var.second[1] - var.first[1]
|
||||||
|
|
||||||
|
# sort() doesn't work as it turns the numbers into strings
|
||||||
|
gcd0 = abs(local.dx) < abs(local.dy) ? [abs(local.dx), abs(local.dy)] : [abs(local.dy), abs(local.dx)]
|
||||||
|
|
||||||
|
# Do as many iterations as necessary.
|
||||||
|
gcd1 = local.gcd0[0] == 0 ? local.gcd0 : [local.gcd0[1] % local.gcd0[0], local.gcd0[0]]
|
||||||
|
gcd2 = local.gcd1[0] == 0 ? local.gcd1 : [local.gcd1[1] % local.gcd1[0], local.gcd1[0]]
|
||||||
|
gcd3 = local.gcd2[0] == 0 ? local.gcd2 : [local.gcd2[1] % local.gcd2[0], local.gcd2[0]]
|
||||||
|
gcd4 = local.gcd3[0] == 0 ? local.gcd3 : [local.gcd3[1] % local.gcd3[0], local.gcd3[0]]
|
||||||
|
gcd5 = local.gcd4[0] == 0 ? local.gcd4 : [local.gcd4[1] % local.gcd4[0], local.gcd4[0]]
|
||||||
|
gcd6 = local.gcd5[0] == 0 ? local.gcd5 : [local.gcd5[1] % local.gcd5[0], local.gcd5[0]]
|
||||||
|
gcd7 = local.gcd6[0] == 0 ? local.gcd6 : [local.gcd6[1] % local.gcd6[0], local.gcd6[0]]
|
||||||
|
gcd8 = local.gcd7[0] == 0 ? local.gcd7 : [local.gcd7[1] % local.gcd7[0], local.gcd7[0]]
|
||||||
|
gcd9 = local.gcd8[0] == 0 ? local.gcd8 : [local.gcd8[1] % local.gcd8[0], local.gcd8[0]]
|
||||||
|
gcd10 = local.gcd9[0] == 0 ? local.gcd9 : [local.gcd9[1] % local.gcd9[0], local.gcd9[0]]
|
||||||
|
|
||||||
|
# 10 iterations should cover numbers up to 55, which is more than the width/height
|
||||||
|
gcd = local.gcd10[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "nodes1" {
|
||||||
|
value = [
|
||||||
|
[var.first[0] - local.dx, var.first[1] - local.dy],
|
||||||
|
[var.second[0] + local.dx, var.second[1] + local.dy],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "nodes2" {
|
||||||
|
value = concat(
|
||||||
|
[for i in range(max(var.width, var.height)) : [var.first[0] - i * local.dx / local.gcd, var.first[1] - i * local.dy / local.gcd]],
|
||||||
|
[for i in range(max(var.width, var.height)) : [var.second[0] + i * local.dx / local.gcd, var.second[1] + i * local.dy / local.gcd]]
|
||||||
|
)
|
||||||
|
}
|
||||||
39
2024/bonus/day08/main.tf
Normal file
39
2024/bonus/day08/main.tf
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
variable "input" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
lines = split("\n", chomp(var.input))
|
||||||
|
height = length(local.lines)
|
||||||
|
width = length(local.lines[0])
|
||||||
|
|
||||||
|
antennae = concat([
|
||||||
|
for y in range(local.height) :
|
||||||
|
[
|
||||||
|
for x in range(local.width) :
|
||||||
|
[substr(local.lines[y], x, 1), x, y]
|
||||||
|
if substr(local.lines[y], x, 1) != "."
|
||||||
|
]
|
||||||
|
]...)
|
||||||
|
|
||||||
|
by_freq = {
|
||||||
|
for antenna in local.antennae :
|
||||||
|
antenna[0] => [antenna[1], antenna[2]]...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "freq" {
|
||||||
|
source = "./freq"
|
||||||
|
for_each = local.by_freq
|
||||||
|
width = local.width
|
||||||
|
height = local.height
|
||||||
|
antennae = each.value
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part1" {
|
||||||
|
value = length(setunion([for _, v in module.freq : v.nodes1]...))
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part2" {
|
||||||
|
value = length(setunion([for _, v in module.freq : v.nodes2]...))
|
||||||
|
}
|
||||||
@@ -2,20 +2,29 @@ variable "prev" {
|
|||||||
type = map(number)
|
type = map(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
module "transform" {
|
|
||||||
source = "../transform"
|
|
||||||
for_each = var.prev
|
|
||||||
|
|
||||||
num = each.key
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
by_value = flatten([
|
by_value = flatten([
|
||||||
for key, value in module.transform :
|
for num, count in var.prev : (
|
||||||
[for result in value.result : { num = result, count = var.prev[key] }]
|
tonumber(num) == 0
|
||||||
|
? [{ number = 1, amount = count }]
|
||||||
|
: (
|
||||||
|
length(tostring(num)) % 2 == 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
number = tonumber(substr(tostring(num), 0, length(tostring(num)) / 2)),
|
||||||
|
amount = count
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number = tonumber(substr(tostring(num), length(tostring(num)) / 2, length(tostring(num)) / 2)),
|
||||||
|
amount = count,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [{ number = 2024 * num, amount = count }]
|
||||||
|
)
|
||||||
|
)
|
||||||
])
|
])
|
||||||
|
|
||||||
grouped = { for kv in local.by_value : kv.num => kv.count... }
|
grouped = { for kv in local.by_value : kv.number => kv.amount... }
|
||||||
}
|
}
|
||||||
|
|
||||||
output "next" {
|
output "next" {
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
variable "num" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
as_str = tostring(var.num)
|
|
||||||
len = length(local.as_str)
|
|
||||||
half = floor(length(local.as_str) / 2)
|
|
||||||
first = try(tonumber(substr(local.as_str, 0, local.half)), -1)
|
|
||||||
second = try(tonumber(substr(local.as_str, local.half, local.half)), -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "result" {
|
|
||||||
value = var.num == 0 ? [1] : local.len % 2 == 0 ? [local.first, local.second] : [var.num * 2024]
|
|
||||||
}
|
|
||||||
31
2024/bonus/day13/main.tf
Normal file
31
2024/bonus/day13/main.tf
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
variable "input" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
machines = regexall(
|
||||||
|
"Button A: X\\+(\\d+), Y\\+(\\d+)\nButton B: X\\+(\\d+), Y\\+(\\d+)\nPrize: X=(\\d+), Y=(\\d+)",
|
||||||
|
var.input
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module "solve1" {
|
||||||
|
source = "./solve"
|
||||||
|
machines = local.machines
|
||||||
|
}
|
||||||
|
|
||||||
|
module "solve2" {
|
||||||
|
source = "./solve"
|
||||||
|
machines = [
|
||||||
|
for machine in local.machines :
|
||||||
|
[machine[0], machine[1], machine[2], machine[3], 10000000000000 + machine[4], 10000000000000 + machine[5]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part1" {
|
||||||
|
value = module.solve1.solutions
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part2" {
|
||||||
|
value = module.solve2.solutions
|
||||||
|
}
|
||||||
34
2024/bonus/day13/solve/main.tf
Normal file
34
2024/bonus/day13/solve/main.tf
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
variable "machines" {
|
||||||
|
type = list(list(number))
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
a_substitutions = [
|
||||||
|
for machine in var.machines :
|
||||||
|
[-machine[2] / machine[0], machine[4] / machine[0]]
|
||||||
|
]
|
||||||
|
|
||||||
|
b_equations = [
|
||||||
|
for i in range(length(var.machines)) :
|
||||||
|
[
|
||||||
|
var.machines[i][3] + local.a_substitutions[i][0] * var.machines[i][1],
|
||||||
|
var.machines[i][5] - local.a_substitutions[i][1] * var.machines[i][1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
b = [for eq in local.b_equations : floor(eq[1] / eq[0] + 0.5)]
|
||||||
|
|
||||||
|
a = [
|
||||||
|
for i in range(length(var.machines)) :
|
||||||
|
floor((var.machines[i][4] - local.b[i] * var.machines[i][2]) / var.machines[i][0] + 0.5)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "solutions" {
|
||||||
|
value = sum([
|
||||||
|
for i in range(length(var.machines)) :
|
||||||
|
3 * local.a[i] + local.b[i]
|
||||||
|
if var.machines[i][0] * local.a[i] + var.machines[i][2] * local.b[i] == var.machines[i][4]
|
||||||
|
&& var.machines[i][1] * local.a[i] + var.machines[i][3] * local.b[i] == var.machines[i][5]
|
||||||
|
])
|
||||||
|
}
|
||||||
33
2024/bonus/day14/main.tf
Normal file
33
2024/bonus/day14/main.tf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
variable "input" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "width" {
|
||||||
|
type = number
|
||||||
|
default = 101
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "height" {
|
||||||
|
type = number
|
||||||
|
default = 103
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
lines = regexall("p=(-?\\d+),(-?\\d+) v=(-?\\d+),(-?\\d+)", var.input)
|
||||||
|
positions = [
|
||||||
|
for line in local.lines :
|
||||||
|
[
|
||||||
|
((line[0] + 100 * line[2]) % var.width + var.width) % var.width,
|
||||||
|
((line[1] + 100 * line[3]) % var.height + var.height) % var.height,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
q1 = length([for pos in local.positions : pos if pos[0] < floor(var.width / 2) && pos[1] < floor(var.height / 2)])
|
||||||
|
q2 = length([for pos in local.positions : pos if pos[0] > floor(var.width / 2) && pos[1] < floor(var.height / 2)])
|
||||||
|
q3 = length([for pos in local.positions : pos if pos[0] < floor(var.width / 2) && pos[1] > floor(var.height / 2)])
|
||||||
|
q4 = length([for pos in local.positions : pos if pos[0] > floor(var.width / 2) && pos[1] > floor(var.height / 2)])
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part1" {
|
||||||
|
value = local.q1 * local.q2 * local.q3 * local.q4
|
||||||
|
}
|
||||||
13
2024/bonus/day19/main.tf
Normal file
13
2024/bonus/day19/main.tf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
variable "input" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
parts = split("\n\n", chomp((var.input)))
|
||||||
|
patterns = replace(local.parts[0], ", ", "|")
|
||||||
|
valid = [for line in split("\n", local.parts[1]) : line if length(regexall("^(${local.patterns})+$", line)) > 0]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part1" {
|
||||||
|
value = length(local.valid)
|
||||||
|
}
|
||||||
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)])
|
||||||
|
}
|
||||||
@@ -66,6 +66,19 @@ output "day05_1" {
|
|||||||
value = module.day05.part1
|
value = module.day05.part1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "day08" {
|
||||||
|
source = "./day08"
|
||||||
|
input = file("../inputs/08.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day08_1" {
|
||||||
|
value = module.day08.part1
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day08_2" {
|
||||||
|
value = module.day08.part2
|
||||||
|
}
|
||||||
|
|
||||||
module "day11" {
|
module "day11" {
|
||||||
source = "./day11"
|
source = "./day11"
|
||||||
input = file("../inputs/11.txt")
|
input = file("../inputs/11.txt")
|
||||||
@@ -78,3 +91,43 @@ output "day11_1" {
|
|||||||
output "day11_2" {
|
output "day11_2" {
|
||||||
value = module.day11.part2
|
value = module.day11.part2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "day13" {
|
||||||
|
source = "./day13"
|
||||||
|
input = file("../inputs/13.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day13_1" {
|
||||||
|
value = module.day13.part1
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day13_2" {
|
||||||
|
value = module.day13.part2
|
||||||
|
}
|
||||||
|
|
||||||
|
module "day14" {
|
||||||
|
source = "./day14"
|
||||||
|
input = file("../inputs/14.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day14_1" {
|
||||||
|
value = module.day14.part1
|
||||||
|
}
|
||||||
|
|
||||||
|
module "day19" {
|
||||||
|
source = "./day19"
|
||||||
|
input = file("../inputs/19.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day19_1" {
|
||||||
|
value = module.day19.part1
|
||||||
|
}
|
||||||
|
|
||||||
|
module "day25" {
|
||||||
|
source = "./day25"
|
||||||
|
input = file("../inputs/25.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
output "day25_1" {
|
||||||
|
value = module.day25.part1
|
||||||
|
}
|
||||||
|
|||||||
@@ -131,6 +131,29 @@ run "day5_1" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run "day08" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
module {
|
||||||
|
source = "./day08"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
input = file("../tests/samples/08.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.part1 == 14
|
||||||
|
error_message = "Part1 output is wrong"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.part2 == 34
|
||||||
|
error_message = "Part1 output is wrong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
run "day11" {
|
run "day11" {
|
||||||
command = plan
|
command = plan
|
||||||
|
|
||||||
@@ -147,3 +170,73 @@ run "day11" {
|
|||||||
error_message = "Part1 output is wrong"
|
error_message = "Part1 output is wrong"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run "day13" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
module {
|
||||||
|
source = "./day13"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
input = file("../tests/samples/13.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.part1 == 480
|
||||||
|
error_message = "Part1 output is wrong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "day14" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
module {
|
||||||
|
source = "./day14"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
input = file("../tests/samples/14.txt")
|
||||||
|
height = 7
|
||||||
|
width = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.part1 == 12
|
||||||
|
error_message = "Part1 output is wrong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "day19" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
module {
|
||||||
|
source = "./day19"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
input = file("../tests/samples/19.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.part1 == 6
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.7",
|
"click>=8.1.7",
|
||||||
|
"networkx>=3.4.2",
|
||||||
"numpy>=2.1.2",
|
"numpy>=2.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class DayRunner(CombinedRunner):
|
|||||||
heapq.heappush(todo, (dist, x, y, dx, dy))
|
heapq.heappush(todo, (dist, x, y, dx, dy))
|
||||||
elif best[x, y, dx, dy][0] == dist:
|
elif best[x, y, dx, dy][0] == dist:
|
||||||
best[x, y, dx, dy][1].append((cx, cy, cdx, cdy))
|
best[x, y, dx, dy][1].append((cx, cy, cdx, cdy))
|
||||||
|
|
||||||
shortest_dist = None
|
shortest_dist = None
|
||||||
finishes = set()
|
finishes = set()
|
||||||
|
|
||||||
|
|||||||
89
2024/src/aoc/days/day17.py
Normal file
89
2024/src/aoc/days/day17.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
|
||||||
|
def run_program(
|
||||||
|
register_a: int, register_b: int, register_c: int, program: list[int]
|
||||||
|
) -> list[int]:
|
||||||
|
ip = 0
|
||||||
|
out = []
|
||||||
|
|
||||||
|
def combo(index: int) -> int:
|
||||||
|
match program[index]:
|
||||||
|
case 0:
|
||||||
|
return 0
|
||||||
|
case 1:
|
||||||
|
return 1
|
||||||
|
case 2:
|
||||||
|
return 2
|
||||||
|
case 3:
|
||||||
|
return 3
|
||||||
|
case 4:
|
||||||
|
return register_a
|
||||||
|
case 5:
|
||||||
|
return register_b
|
||||||
|
case 6:
|
||||||
|
return register_c
|
||||||
|
|
||||||
|
while ip < len(program):
|
||||||
|
match program[ip]:
|
||||||
|
case 0: # adv
|
||||||
|
register_a = register_a // 2 ** combo(ip + 1)
|
||||||
|
case 1: # bxl
|
||||||
|
register_b ^= program[ip + 1]
|
||||||
|
case 2: # bst
|
||||||
|
register_b = combo(ip + 1) & 0x7
|
||||||
|
case 3: # jnz
|
||||||
|
if register_a != 0:
|
||||||
|
ip = program[ip + 1]
|
||||||
|
continue
|
||||||
|
case 4: # bxc
|
||||||
|
register_b ^= register_c
|
||||||
|
case 5: # out
|
||||||
|
out.append(combo(ip + 1) & 7)
|
||||||
|
case 6: # bdv
|
||||||
|
register_b = register_a // 2 ** combo(ip + 1)
|
||||||
|
case 7: # cdv
|
||||||
|
register_c = register_a // 2 ** combo(ip + 1)
|
||||||
|
ip += 2
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(cls, input: str) -> str:
|
||||||
|
numbers = re.findall(r"\d+", input)
|
||||||
|
|
||||||
|
register_a, register_b, register_c = map(int, numbers[:3])
|
||||||
|
program = list(map(int, numbers[3:]))
|
||||||
|
|
||||||
|
out = run_program(register_a, register_b, register_c, program)
|
||||||
|
|
||||||
|
return ",".join(map(str, out))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str) -> int:
|
||||||
|
numbers = re.findall(r"\d+", input)
|
||||||
|
|
||||||
|
_, register_b, register_c = map(int, numbers[:3])
|
||||||
|
program = list(map(int, numbers[3:]))
|
||||||
|
|
||||||
|
cur = [0]
|
||||||
|
|
||||||
|
# It came to me in a dream
|
||||||
|
for entry in reversed(program):
|
||||||
|
next_gen = []
|
||||||
|
|
||||||
|
for num in cur:
|
||||||
|
num *= 8
|
||||||
|
for n in range(8):
|
||||||
|
output = run_program(num + n, register_b, register_c, program)
|
||||||
|
result = output[0]
|
||||||
|
if result == entry:
|
||||||
|
next_gen.append(num + n)
|
||||||
|
|
||||||
|
cur = next_gen
|
||||||
|
|
||||||
|
return cur[0]
|
||||||
70
2024/src/aoc/days/day18.py
Normal file
70
2024/src/aoc/days/day18.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(data: str) -> list[tuple[int, int]]:
|
||||||
|
return [tuple(map(int, line.split(","))) for line in data.strip().split("\n")]
|
||||||
|
|
||||||
|
|
||||||
|
def find_exit(fallen: set[tuple[int, int]], width: int, height: int) -> int | None:
|
||||||
|
todo = deque([(0, 0, 0)])
|
||||||
|
|
||||||
|
best = {(0, 0): 0}
|
||||||
|
|
||||||
|
def enqueue(dist: int, x: int, y: int):
|
||||||
|
# print(f"trying {x},{y}")
|
||||||
|
if (x, y) in fallen:
|
||||||
|
return
|
||||||
|
|
||||||
|
if (x, y) not in best or best[x, y] > dist:
|
||||||
|
best[x, y] = dist
|
||||||
|
todo.append((dist, x, y))
|
||||||
|
|
||||||
|
while todo:
|
||||||
|
dist, x, y = todo.popleft()
|
||||||
|
# print(x, y)
|
||||||
|
|
||||||
|
if x == width - 1 and y == height - 1:
|
||||||
|
return dist
|
||||||
|
|
||||||
|
if x > 0:
|
||||||
|
enqueue(dist + 1, x - 1, y)
|
||||||
|
|
||||||
|
if x + 1 < width:
|
||||||
|
enqueue(dist + 1, x + 1, y)
|
||||||
|
|
||||||
|
if y > 0:
|
||||||
|
enqueue(dist + 1, x, y - 1)
|
||||||
|
|
||||||
|
if y + 1 < height:
|
||||||
|
enqueue(dist + 1, x, y + 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(
|
||||||
|
cls, input: str, width: int = 71, height: int = 71, limit: int = 1024
|
||||||
|
) -> int:
|
||||||
|
falling = parse_input(input)
|
||||||
|
|
||||||
|
return find_exit(set(falling[:limit]), width, height)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str, width: int = 71, height: int = 71) -> str:
|
||||||
|
falling = parse_input(input)
|
||||||
|
|
||||||
|
lower = 0
|
||||||
|
upper = len(falling)
|
||||||
|
|
||||||
|
while lower < upper:
|
||||||
|
mid = lower + (upper - lower) // 2
|
||||||
|
|
||||||
|
if find_exit(set(falling[:mid]), width, height) is not None:
|
||||||
|
lower = mid + 1
|
||||||
|
else:
|
||||||
|
upper = mid
|
||||||
|
|
||||||
|
first_blocker = falling[lower - 1]
|
||||||
|
|
||||||
|
return f"{first_blocker[0]},{first_blocker[1]}"
|
||||||
41
2024/src/aoc/days/day19.py
Normal file
41
2024/src/aoc/days/day19.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from . import CombinedRunner
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(data: str) -> tuple[list[str], list[str]]:
|
||||||
|
patterns, designs = data.strip().split("\n\n")
|
||||||
|
|
||||||
|
return patterns.split(", "), designs.split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(CombinedRunner):
|
||||||
|
@classmethod
|
||||||
|
def run_both(cls, input: str) -> int:
|
||||||
|
patterns, designs = parse_input(input)
|
||||||
|
|
||||||
|
by_prefix = collections.defaultdict(list)
|
||||||
|
for prefix in patterns:
|
||||||
|
by_prefix[prefix[0]].append(prefix)
|
||||||
|
|
||||||
|
possible = 0
|
||||||
|
ways = 0
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def is_possible(design: str) -> bool:
|
||||||
|
if not design:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return sum(
|
||||||
|
is_possible(design[len(prefix) :])
|
||||||
|
for prefix in by_prefix[design[0]]
|
||||||
|
if design.startswith(prefix)
|
||||||
|
)
|
||||||
|
|
||||||
|
for design in designs:
|
||||||
|
if (solve := is_possible(design)) > 0:
|
||||||
|
possible += 1
|
||||||
|
ways += solve
|
||||||
|
|
||||||
|
return possible, ways
|
||||||
82
2024/src/aoc/days/day20.py
Normal file
82
2024/src/aoc/days/day20.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
DIRECTIONS = [
|
||||||
|
(-1, 0),
|
||||||
|
(1, 0),
|
||||||
|
(0, -1),
|
||||||
|
(0, 1),
|
||||||
|
]
|
||||||
|
|
||||||
|
CHEATS = [
|
||||||
|
(-2, 0),
|
||||||
|
(2, 0),
|
||||||
|
(0, -2),
|
||||||
|
(0, 2),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_path(input: str) -> dict[tuple[int, int], int]:
|
||||||
|
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
||||||
|
|
||||||
|
ys, xs = numpy.nonzero(grid == "S")
|
||||||
|
sx, sy = int(xs[0]), int(ys[0])
|
||||||
|
|
||||||
|
nx, ny = sx, sy
|
||||||
|
|
||||||
|
path = {
|
||||||
|
(sx, sy): 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
while grid[ny, nx] != "E":
|
||||||
|
x, y = nx, ny
|
||||||
|
|
||||||
|
for dx, dy in DIRECTIONS:
|
||||||
|
if grid[y + dy, x + dx] == "#" or (x + dx, y + dy) in path:
|
||||||
|
continue
|
||||||
|
nx = x + dx
|
||||||
|
ny = y + dy
|
||||||
|
break
|
||||||
|
|
||||||
|
path[nx, ny] = len(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_savings(a: tuple[tuple[int, int], int], b: tuple[tuple[int, int], int]) -> int:
|
||||||
|
(ax, ay), ad = a
|
||||||
|
(bx, by), bd = b
|
||||||
|
|
||||||
|
dist = abs(bx - ax) + abs(by - ay)
|
||||||
|
if dist <= 20:
|
||||||
|
return bd - ad - dist
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(cls, input: str, limit: int = 100) -> int:
|
||||||
|
path = parse_path(input)
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for (px, py), dist in path.items():
|
||||||
|
for dx, dy in CHEATS:
|
||||||
|
if (other := path.get((px + dx, py + dy))) is not None:
|
||||||
|
savings = dist - other - 2
|
||||||
|
if savings >= limit:
|
||||||
|
total += 1
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str, limit: int = 100) -> int:
|
||||||
|
path = parse_path(input)
|
||||||
|
|
||||||
|
return sum(
|
||||||
|
get_savings(a, b) >= limit
|
||||||
|
for a, b in itertools.combinations(path.items(), 2)
|
||||||
|
)
|
||||||
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
|
||||||
41
2024/src/aoc/days/day22.py
Normal file
41
2024/src/aoc/days/day22.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
from numpy.lib.stride_tricks import sliding_window_view
|
||||||
|
|
||||||
|
from . import CombinedRunner
|
||||||
|
|
||||||
|
|
||||||
|
def advance(secrets: numpy.array) -> numpy.array:
|
||||||
|
new_secrets = (secrets ^ (secrets << 6)) & 0xFFFFFF
|
||||||
|
new_secrets ^= new_secrets >> 5
|
||||||
|
new_secrets ^= new_secrets << 11
|
||||||
|
new_secrets &= 0xFFFFFF
|
||||||
|
|
||||||
|
return new_secrets
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(CombinedRunner):
|
||||||
|
@classmethod
|
||||||
|
def run_both(cls, input: str) -> tuple[int, int]:
|
||||||
|
secrets = numpy.fromstring(input, dtype=int, sep="\n")
|
||||||
|
|
||||||
|
progression = [secrets]
|
||||||
|
|
||||||
|
for _ in range(2000):
|
||||||
|
secrets = advance(secrets)
|
||||||
|
progression.append(secrets)
|
||||||
|
|
||||||
|
field = numpy.stack(progression, axis=-1) % 10
|
||||||
|
delta = field[:, 1:] - field[:, :-1]
|
||||||
|
windows = sliding_window_view(delta, 4, axis=1)
|
||||||
|
|
||||||
|
per_signal = defaultdict(int)
|
||||||
|
|
||||||
|
for row_scores, row_deltas in zip(field, windows):
|
||||||
|
unique, positions = numpy.unique(row_deltas, return_index=True, axis=0)
|
||||||
|
|
||||||
|
for key, bananas in zip(unique, row_scores[positions + 4]):
|
||||||
|
per_signal[tuple(key)] += bananas
|
||||||
|
|
||||||
|
return secrets.sum(), max(per_signal.values())
|
||||||
42
2024/src/aoc/days/day23.py
Normal file
42
2024/src/aoc/days/day23.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import networkx
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(cls, input: str) -> int:
|
||||||
|
edges = defaultdict(set)
|
||||||
|
|
||||||
|
for line in input.strip().split("\n"):
|
||||||
|
a, b = line.split("-")
|
||||||
|
edges[a].add(b)
|
||||||
|
edges[b].add(a)
|
||||||
|
|
||||||
|
found = set()
|
||||||
|
|
||||||
|
for a, out in edges.items():
|
||||||
|
if a[0] != "t":
|
||||||
|
continue
|
||||||
|
|
||||||
|
for b in out:
|
||||||
|
for c in edges[b]:
|
||||||
|
if c in out:
|
||||||
|
found.add(tuple(sorted([a, b, c])))
|
||||||
|
|
||||||
|
return len(found)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str) -> str:
|
||||||
|
graph = networkx.Graph()
|
||||||
|
|
||||||
|
for line in input.strip().split("\n"):
|
||||||
|
a, b = line.split("-")
|
||||||
|
graph.add_edge(a, b)
|
||||||
|
|
||||||
|
cliques = networkx.find_cliques(graph)
|
||||||
|
max_clique = max(cliques, key=len)
|
||||||
|
|
||||||
|
return ",".join(sorted(max_clique))
|
||||||
133
2024/src/aoc/days/day24.py
Normal file
133
2024/src/aoc/days/day24.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import functools
|
||||||
|
import re
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(input: str) -> tuple[dict[str, int], dict[str, tuple[str, str, str]]]:
|
||||||
|
variable_part, rules_part = input.strip().split("\n\n")
|
||||||
|
|
||||||
|
variables = {}
|
||||||
|
|
||||||
|
for line in variable_part.splitlines():
|
||||||
|
variable, value = line.split(": ")
|
||||||
|
variables[variable] = int(value)
|
||||||
|
|
||||||
|
rules = {}
|
||||||
|
|
||||||
|
for first, op, second, result in re.findall(
|
||||||
|
r"(\w+) (XOR|OR|AND) (\w+) -> (\w+)", rules_part
|
||||||
|
):
|
||||||
|
rules[result] = (first, op, second)
|
||||||
|
|
||||||
|
return variables, rules
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(cls, input: str) -> int:
|
||||||
|
variables, rules = parse_input(input)
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_value(variable: str) -> int:
|
||||||
|
if variable in variables:
|
||||||
|
return variables[variable]
|
||||||
|
|
||||||
|
first, op, second = rules[variable]
|
||||||
|
first_v = get_value(first)
|
||||||
|
second_v = get_value(second)
|
||||||
|
|
||||||
|
match op:
|
||||||
|
case "AND":
|
||||||
|
return first_v & second_v
|
||||||
|
case "OR":
|
||||||
|
return first_v | second_v
|
||||||
|
case "XOR":
|
||||||
|
return first_v ^ second_v
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
for variable in reversed(sorted(rules)):
|
||||||
|
if not variable.startswith("z"):
|
||||||
|
continue
|
||||||
|
result = result * 2 + get_value(variable)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str) -> str:
|
||||||
|
variables, rules = parse_input(input)
|
||||||
|
|
||||||
|
max_bit = int(
|
||||||
|
max(variable for variable in rules if variable.startswith("z"))[1:]
|
||||||
|
)
|
||||||
|
|
||||||
|
def find_invalid(output: str, pattern) -> set[str]:
|
||||||
|
if pattern is None:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
if output in rules:
|
||||||
|
left, op, right = rules[output]
|
||||||
|
elif output == pattern:
|
||||||
|
return set()
|
||||||
|
else:
|
||||||
|
return {output}
|
||||||
|
|
||||||
|
pop, pleft, pright = pattern
|
||||||
|
|
||||||
|
if op != pop:
|
||||||
|
return {output}
|
||||||
|
|
||||||
|
wrong_normal = find_invalid(left, pleft) | find_invalid(right, pright)
|
||||||
|
wrong_mirror = find_invalid(left, pright) | find_invalid(right, pleft)
|
||||||
|
|
||||||
|
least_wrong = min(wrong_mirror, wrong_normal, key=len)
|
||||||
|
|
||||||
|
return least_wrong
|
||||||
|
|
||||||
|
assert max_bit >= 3
|
||||||
|
|
||||||
|
# First one is a half adder, that's a simple pattern
|
||||||
|
invalid = find_invalid("z00", ["XOR", "x00", "y00"])
|
||||||
|
# Second one is missing a reference to the before-previous adder, so it's a
|
||||||
|
# slightly different patterns
|
||||||
|
invalid |= find_invalid(
|
||||||
|
"z01", ["XOR", ["AND", "x00", "y00"], ["XOR", "x01", "y01"]]
|
||||||
|
)
|
||||||
|
# Needed a second lookback to determine that `ktp` is valid
|
||||||
|
invalid |= find_invalid(
|
||||||
|
"z02",
|
||||||
|
[
|
||||||
|
"XOR",
|
||||||
|
["XOR", "x02", "y02"],
|
||||||
|
[
|
||||||
|
"OR",
|
||||||
|
["AND", "x01", "y01"],
|
||||||
|
["AND", ["XOR", "x01", "y01"], ["AND", "x00", "y00"]],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
for n in range(3, max_bit):
|
||||||
|
xcurr = f"x{n:02}"
|
||||||
|
ycurr = f"y{n:02}"
|
||||||
|
zcurr = f"z{n:02}"
|
||||||
|
xprev = f"x{n-1:02}"
|
||||||
|
yprev = f"y{n-1:02}"
|
||||||
|
|
||||||
|
invalid |= find_invalid(
|
||||||
|
zcurr,
|
||||||
|
[
|
||||||
|
"XOR",
|
||||||
|
["XOR", xcurr, ycurr],
|
||||||
|
[
|
||||||
|
"OR",
|
||||||
|
["AND", xprev, yprev],
|
||||||
|
["AND", ["XOR", xprev, yprev], ["OR", None, None]],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# This code somehow believes `ktp` is invalid, but it's fine on closer
|
||||||
|
# inspection. Will figure that out later.
|
||||||
|
|
||||||
|
return ",".join(sorted(invalid))
|
||||||
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/17.1.txt
Normal file
5
2024/tests/samples/17.1.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Register A: 729
|
||||||
|
Register B: 0
|
||||||
|
Register C: 0
|
||||||
|
|
||||||
|
Program: 0,1,5,4,3,0
|
||||||
5
2024/tests/samples/17.2.txt
Normal file
5
2024/tests/samples/17.2.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Register A: 2024
|
||||||
|
Register B: 0
|
||||||
|
Register C: 0
|
||||||
|
|
||||||
|
Program: 0,3,5,4,3,0
|
||||||
25
2024/tests/samples/18.txt
Normal file
25
2024/tests/samples/18.txt
Normal 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
2024/tests/samples/19.txt
Normal file
10
2024/tests/samples/19.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
r, wr, b, g, bwu, rb, gb, br
|
||||||
|
|
||||||
|
brwrr
|
||||||
|
bggr
|
||||||
|
gbbr
|
||||||
|
rrbgbr
|
||||||
|
ubwu
|
||||||
|
bwurrg
|
||||||
|
brgr
|
||||||
|
bbrgwb
|
||||||
15
2024/tests/samples/20.txt
Normal file
15
2024/tests/samples/20.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
###############
|
||||||
|
#...#...#.....#
|
||||||
|
#.#.#.#.#.###.#
|
||||||
|
#S#...#.#.#...#
|
||||||
|
#######.#.#.###
|
||||||
|
#######.#.#...#
|
||||||
|
#######.#.###.#
|
||||||
|
###..E#...#...#
|
||||||
|
###.#######.###
|
||||||
|
#...###...#...#
|
||||||
|
#.#####.#.###.#
|
||||||
|
#.#...#.#.#...#
|
||||||
|
#.#.#.#.#.#.###
|
||||||
|
#...#...#...###
|
||||||
|
###############
|
||||||
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
|
||||||
4
2024/tests/samples/22.1.txt
Normal file
4
2024/tests/samples/22.1.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
1
|
||||||
|
10
|
||||||
|
100
|
||||||
|
2024
|
||||||
4
2024/tests/samples/22.2.txt
Normal file
4
2024/tests/samples/22.2.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
2024
|
||||||
32
2024/tests/samples/23.txt
Normal file
32
2024/tests/samples/23.txt
Normal 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
|
||||||
47
2024/tests/samples/24.txt
Normal file
47
2024/tests/samples/24.txt
Normal 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
2024/tests/samples/25.txt
Normal file
39
2024/tests/samples/25.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#####
|
||||||
|
.####
|
||||||
|
.####
|
||||||
|
.####
|
||||||
|
.#.#.
|
||||||
|
.#...
|
||||||
|
.....
|
||||||
|
|
||||||
|
#####
|
||||||
|
##.##
|
||||||
|
.#.##
|
||||||
|
...##
|
||||||
|
...#.
|
||||||
|
...#.
|
||||||
|
.....
|
||||||
|
|
||||||
|
.....
|
||||||
|
#....
|
||||||
|
#....
|
||||||
|
#...#
|
||||||
|
#.#.#
|
||||||
|
#.###
|
||||||
|
#####
|
||||||
|
|
||||||
|
.....
|
||||||
|
.....
|
||||||
|
#.#..
|
||||||
|
###..
|
||||||
|
###.#
|
||||||
|
###.#
|
||||||
|
#####
|
||||||
|
|
||||||
|
.....
|
||||||
|
.....
|
||||||
|
.....
|
||||||
|
#....
|
||||||
|
#.#..
|
||||||
|
#.#.#
|
||||||
|
#####
|
||||||
@@ -14,3 +14,14 @@ from . import get_data
|
|||||||
)
|
)
|
||||||
def test_sample_part1(data: str, result: int) -> None:
|
def test_sample_part1(data: str, result: int) -> None:
|
||||||
assert DayRunner.part1(data) == result
|
assert DayRunner.part1(data) == result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data,result",
|
||||||
|
[
|
||||||
|
(get_data(16, 1), 45),
|
||||||
|
(get_data(16, 2), 64),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sample_part2(data: str, result: int) -> None:
|
||||||
|
assert DayRunner.part2(data) == result
|
||||||
|
|||||||
11
2024/tests/test_day17.py
Normal file
11
2024/tests/test_day17.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day17 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(17, 1)) == "4,6,3,5,6,3,5,2,1,0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(17, 2)) == 117440
|
||||||
11
2024/tests/test_day18.py
Normal file
11
2024/tests/test_day18.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day18 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(18), width=7, height=7, limit=12) == 22
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(18), width=7, height=7) == "6,1"
|
||||||
11
2024/tests/test_day19.py
Normal file
11
2024/tests/test_day19.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day19 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(19)) == 6
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(19)) == 16
|
||||||
11
2024/tests/test_day20.py
Normal file
11
2024/tests/test_day20.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day20 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(20), limit=1) == 44
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(20), limit=50) == 285
|
||||||
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
|
||||||
11
2024/tests/test_day22.py
Normal file
11
2024/tests/test_day22.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day22 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(22, 1)) == 37327623
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(22, 2)) == 23
|
||||||
11
2024/tests/test_day23.py
Normal file
11
2024/tests/test_day23.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from aoc.days.day23 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(23)) == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part2() -> None:
|
||||||
|
assert DayRunner.part2(get_data(23)) == "co,de,ka,ta"
|
||||||
7
2024/tests/test_day24.py
Normal file
7
2024/tests/test_day24.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from aoc.days.day24 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_part1() -> None:
|
||||||
|
assert DayRunner.part1(get_data(24)) == 2024
|
||||||
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
|
||||||
13
2024/uv.lock
generated
13
2024/uv.lock
generated
@@ -7,6 +7,7 @@ version = "0.1.0"
|
|||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
{ name = "networkx" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ dev = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "click", specifier = ">=8.1.7" },
|
{ name = "click", specifier = ">=8.1.7" },
|
||||||
|
{ name = "networkx", specifier = ">=3.4.2" },
|
||||||
{ name = "numpy", specifier = ">=2.1.2" },
|
{ name = "numpy", specifier = ">=2.1.2" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ name = "click"
|
|||||||
version = "8.1.7"
|
version = "8.1.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -92,6 +94,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
|
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "networkx"
|
||||||
|
version = "3.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
|
|||||||
5
2025/README.md
Normal file
5
2025/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Advent of Code 2025
|
||||||
|
|
||||||
|
New year, new challenge. With the shorter year, I'm going to try to use a different language every
|
||||||
|
day, restricting myself to programming languages my employer uses in production. Let's call it the
|
||||||
|
**Full Stack Challenge**.
|
||||||
17
2025/day01/README.md
Normal file
17
2025/day01/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Day 01: Nix
|
||||||
|
|
||||||
|
Nix is a functional language made for the Nix package manager. As
|
||||||
|
|
||||||
|
To run the solution program, start the `nix` repl with the solution program and call the `solve`
|
||||||
|
function with the path to the input file.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ nix repl --option max-call-depth 10000 --file solve.nix
|
||||||
|
nix-repl> solve ./sample.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Some observations:
|
||||||
|
|
||||||
|
- The `max-call-depth` needs to be bumped to at least 10k for the main input files, otherwise you
|
||||||
|
hit the limit in the recursion.
|
||||||
|
- The standard library is lacking several basics but most of those you can build yourself
|
||||||
10
2025/day01/sample.txt
Normal file
10
2025/day01/sample.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
L68
|
||||||
|
L30
|
||||||
|
R48
|
||||||
|
L5
|
||||||
|
R60
|
||||||
|
L55
|
||||||
|
L1
|
||||||
|
L99
|
||||||
|
R14
|
||||||
|
L82
|
||||||
37
2025/day01/solve.nix
Normal file
37
2025/day01/solve.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
solve = input_file:
|
||||||
|
let
|
||||||
|
data = builtins.readFile input_file;
|
||||||
|
lines = builtins.filter (s: s != "" && builtins.isString s) (builtins.split "\n" data);
|
||||||
|
|
||||||
|
# Basic maths because the standard library doesn't have it
|
||||||
|
mod = a: b: a - (a / b) * b;
|
||||||
|
abs = n: if n < 0 then -n else n;
|
||||||
|
|
||||||
|
recurse = list: position: score1: score2:
|
||||||
|
let
|
||||||
|
first = builtins.head list;
|
||||||
|
# This is not guaranteed to work but it's good enough
|
||||||
|
num_part = builtins.substring 1 999 first;
|
||||||
|
num = builtins.fromJSON num_part;
|
||||||
|
delta = mod (if (builtins.substring 0 1 first) == "R" then num else -num) 100;
|
||||||
|
next = mod (position + delta + 100) 100;
|
||||||
|
|
||||||
|
score = if next == 0 then 1 else 0;
|
||||||
|
circles = num / 100;
|
||||||
|
|
||||||
|
passed = if position == 0 then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
if delta < 0 && delta + position <= 0 || delta > 0 && delta + position >= 100 then
|
||||||
|
1
|
||||||
|
else
|
||||||
|
0;
|
||||||
|
in
|
||||||
|
if list == [] then
|
||||||
|
[score1 score2]
|
||||||
|
else
|
||||||
|
recurse (builtins.tail list) next (score + score1) (score2 + passed + circles);
|
||||||
|
in
|
||||||
|
recurse lines 50 0 0;
|
||||||
|
}
|
||||||
29
2025/day02/README.md
Normal file
29
2025/day02/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Day 02: Terraform
|
||||||
|
|
||||||
|
The code assumes an input file at `../inputs/02.txt`. Other than that, simply try to run as follows:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ terraform init
|
||||||
|
…
|
||||||
|
Initializing the backend...
|
||||||
|
Initializing modules...
|
||||||
|
Initializing provider plugins...
|
||||||
|
|
||||||
|
Terraform has been successfully initialized!
|
||||||
|
|
||||||
|
You may now begin working with Terraform. Try running "terraform plan" to see
|
||||||
|
any changes that are required for your infrastructure. All Terraform commands
|
||||||
|
should now work.
|
||||||
|
|
||||||
|
If you ever set or change modules or backend configuration for Terraform,
|
||||||
|
rerun this command to reinitialize your working directory. If you forget, other
|
||||||
|
commands will detect it and remind you to do so if necessary.
|
||||||
|
$ terraform plan
|
||||||
|
….
|
||||||
|
|
||||||
|
Changes to Outputs:
|
||||||
|
+ part1 = secret
|
||||||
|
+ part2 = also secret
|
||||||
|
|
||||||
|
You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.
|
||||||
|
```
|
||||||
29
2025/day02/main.tf
Normal file
29
2025/day02/main.tf
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
locals {
|
||||||
|
input = file("../inputs/02.txt")
|
||||||
|
ranges = split(",", chomp(local.input))
|
||||||
|
min_max = [for r in local.ranges : split("-", r)]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "check_range" {
|
||||||
|
source = "./range"
|
||||||
|
count = length(local.min_max)
|
||||||
|
|
||||||
|
min = local.min_max[count.index][0]
|
||||||
|
max = local.min_max[count.index][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "check_range2" {
|
||||||
|
source = "./range2"
|
||||||
|
count = length(local.min_max)
|
||||||
|
|
||||||
|
min = local.min_max[count.index][0]
|
||||||
|
max = local.min_max[count.index][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part1" {
|
||||||
|
value = sum(module.check_range[*].invalid_sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
output "part2" {
|
||||||
|
value = sum(module.check_range2[*].invalid_sum)
|
||||||
|
}
|
||||||
15
2025/day02/range/item/main.tf
Normal file
15
2025/day02/range/item/main.tf
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
variable "part" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "repetitions" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
repeated = [for _ in range(var.repetitions) : tostring(var.part)]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "full" {
|
||||||
|
value = join("", local.repeated)
|
||||||
|
}
|
||||||
46
2025/day02/range/main.tf
Normal file
46
2025/day02/range/main.tf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
variable "min" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "max" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "repetitions" {
|
||||||
|
type = number
|
||||||
|
default = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
digits = floor(length(tostring(var.max)) / var.repetitions)
|
||||||
|
maximum = substr(tostring(var.max), 0, local.digits)
|
||||||
|
real_maximum = length(tostring(var.max)) % var.repetitions == 0 ? tonumber(local.maximum) : pow(10, local.digits)
|
||||||
|
|
||||||
|
minimum_prefix = substr(tostring(var.min), 0, length(tostring(var.min)) - local.digits * (var.repetitions - 1))
|
||||||
|
minimum = local.minimum_prefix == "" ? 1 : tonumber(local.minimum_prefix)
|
||||||
|
|
||||||
|
count = max(local.real_maximum - local.minimum + 1, 1)
|
||||||
|
|
||||||
|
can_work = anytrue([for n in range(length(tostring(var.min)), length(tostring(var.max)) + 1) : n % var.repetitions == 0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// This "candidates" module ought really be a list comprehension from range, but Terraform does not
|
||||||
|
// allow you to create ranges longer than 1024.
|
||||||
|
module "candidates" {
|
||||||
|
source = "./item"
|
||||||
|
count = local.can_work ? local.count : 0
|
||||||
|
part = count.index + local.minimum
|
||||||
|
repetitions = var.repetitions
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
invalid = [for n in module.candidates[*].full : n if n >= var.min && n <= var.max]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "invalid_sum" {
|
||||||
|
value = length(local.invalid) > 0 ? sum(local.invalid) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
output "invalid" {
|
||||||
|
value = toset(local.invalid)
|
||||||
|
}
|
||||||
27
2025/day02/range2/main.tf
Normal file
27
2025/day02/range2/main.tf
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
variable "min" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "max" {
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
digits = length(tostring(var.max))
|
||||||
|
}
|
||||||
|
|
||||||
|
module "range" {
|
||||||
|
source = "../range"
|
||||||
|
count = local.digits - 1
|
||||||
|
max = var.max
|
||||||
|
min = var.min
|
||||||
|
repetitions = count.index + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
results = setunion(module.range[*].invalid...)
|
||||||
|
}
|
||||||
|
|
||||||
|
output "invalid_sum" {
|
||||||
|
value = length(local.results) > 0 ? sum(local.results) : 0
|
||||||
|
}
|
||||||
128
2025/day02/tests.tftest.hcl
Normal file
128
2025/day02/tests.tftest.hcl
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
run "sample-1-1" {
|
||||||
|
module {
|
||||||
|
source = "./range"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 11
|
||||||
|
max = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 33
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-1-2" {
|
||||||
|
module {
|
||||||
|
source = "./range"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 95
|
||||||
|
max = 115
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 99
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-1" {
|
||||||
|
module {
|
||||||
|
source = "./range2"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 11
|
||||||
|
max = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 33
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-2" {
|
||||||
|
module {
|
||||||
|
source = "./range2"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 95
|
||||||
|
max = 115
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 210
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-3" {
|
||||||
|
module {
|
||||||
|
source = "./range2"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 998
|
||||||
|
max = 1012
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 2009
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-2-detail" {
|
||||||
|
module {
|
||||||
|
source = "./range"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 95
|
||||||
|
max = 115
|
||||||
|
repetitions = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 111
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-10" {
|
||||||
|
module {
|
||||||
|
source = "./range2"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 824824821
|
||||||
|
max = 824824827
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 824824824
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "sample-2-11" {
|
||||||
|
module {
|
||||||
|
source = "./range2"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
min = 2121212118
|
||||||
|
max = 2121212124
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = output.invalid_sum == 2121212121
|
||||||
|
error_message = "Incorrect result"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
2025/day03/README.md
Normal file
4
2025/day03/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Day 03: Bash
|
||||||
|
|
||||||
|
Simple as can be, simply run `./solve.sh <input file>` and get both answers. While I allowed myself
|
||||||
|
to use `coreutils` with bash, they turned out to be entirely unnecessary. Pure bash just works.
|
||||||
4
2025/day03/sample.txt
Normal file
4
2025/day03/sample.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
987654321111111
|
||||||
|
811111111111119
|
||||||
|
234234234234278
|
||||||
|
818181911112111
|
||||||
64
2025/day03/solve.sh
Executable file
64
2025/day03/solve.sh
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ $# -lt 1 ]]; then
|
||||||
|
echo "Usage: $0 <input file>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
joltage() {
|
||||||
|
local first second c i
|
||||||
|
|
||||||
|
first=0
|
||||||
|
second=0
|
||||||
|
|
||||||
|
for ((i = 0; i < ${#1}; i++)); do
|
||||||
|
c="${1:$i:1}"
|
||||||
|
|
||||||
|
if [[ $second -gt $first ]]; then
|
||||||
|
first="$second"
|
||||||
|
second="$c"
|
||||||
|
elif [[ $c -gt $second ]]; then
|
||||||
|
second="$c"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$first$second"
|
||||||
|
}
|
||||||
|
|
||||||
|
joltage2() {
|
||||||
|
local c i d e n digits
|
||||||
|
|
||||||
|
digits=(0 0 0 0 0 0 0 0 0 0 0 0)
|
||||||
|
|
||||||
|
for ((i = 0; i < ${#1}; i++)); do
|
||||||
|
c="${1:$i:1}"
|
||||||
|
|
||||||
|
for ((d = 0; d < 11; d++)); do
|
||||||
|
if [[ ${digits[((d + 1))]} -gt ${digits[$d]} ]]; then
|
||||||
|
for ((e = d; e < 11; e++)); do
|
||||||
|
n=$((e + 1))
|
||||||
|
digits[e]=${digits[n]}
|
||||||
|
done
|
||||||
|
digits[11]="0"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $c -gt ${digits[11]} ]]; then
|
||||||
|
digits[11]="$c"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s" "${digits[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
total=0
|
||||||
|
total2=0
|
||||||
|
|
||||||
|
while IFS="" read -r line || [[ -n "$p" ]]; do
|
||||||
|
((total += $(joltage "$line")))
|
||||||
|
((total2 += $(joltage2 "$line")))
|
||||||
|
done <"$1"
|
||||||
|
|
||||||
|
echo "$total"
|
||||||
|
echo "$total2"
|
||||||
11
2025/day04/README.md
Normal file
11
2025/day04/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Day 04: SQLite sql
|
||||||
|
|
||||||
|
Did you know SQLite does not have procedural extensions? I did not when I started this. Regardless,
|
||||||
|
to run:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sqlite3 < solve.sql
|
||||||
|
part1
|
||||||
|
-----
|
||||||
|
13
|
||||||
|
```
|
||||||
10
2025/day04/sample.txt
Normal file
10
2025/day04/sample.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
..@@.@@@@.
|
||||||
|
@@@.@.@.@@
|
||||||
|
@@@@@.@.@@
|
||||||
|
@.@@@@..@.
|
||||||
|
@@.@@@@.@@
|
||||||
|
.@@@@@@@.@
|
||||||
|
.@.@.@.@@@
|
||||||
|
@.@@@.@@@@
|
||||||
|
.@@@@@@@@.
|
||||||
|
@.@.@@@.@.
|
||||||
759
2025/day04/solve.sql
Normal file
759
2025/day04/solve.sql
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
-- First import raw data into a table so we can work
|
||||||
|
CREATE TABLE raw_data(line_data VARCHAR NOT NULL);
|
||||||
|
.import --csv './sample.txt' raw_data
|
||||||
|
-- Then use the auto-incrementing ID to add a y coordinate
|
||||||
|
CREATE TABLE grid_lines(
|
||||||
|
y INTEGER PRIMARY KEY,
|
||||||
|
line_data VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO grid_lines(line_data)
|
||||||
|
SELECT line_data
|
||||||
|
FROM raw_data;
|
||||||
|
-- Now create a table to hold the paper rolls
|
||||||
|
CREATE TABLE rolls(
|
||||||
|
x INTEGER NOT NULL,
|
||||||
|
y INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (x, y)
|
||||||
|
);
|
||||||
|
WITH RECURSIVE cte AS (
|
||||||
|
SELECT y,
|
||||||
|
1 x,
|
||||||
|
line_data,
|
||||||
|
substr(line_data, 1, 1) c
|
||||||
|
FROM grid_lines
|
||||||
|
UNION ALL
|
||||||
|
SELECT y,
|
||||||
|
x + 1,
|
||||||
|
line_data,
|
||||||
|
substr(line_data, x + 1, 1)
|
||||||
|
FROM cte
|
||||||
|
WHERE x <= length(line_data)
|
||||||
|
)
|
||||||
|
INSERT INTO rolls
|
||||||
|
SELECT x,
|
||||||
|
y
|
||||||
|
FROM cte
|
||||||
|
WHERE c = '@';
|
||||||
|
-- Now compute part 1
|
||||||
|
SELECT COUNT(*) as part1
|
||||||
|
FROM rolls r
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls o
|
||||||
|
WHERE o.x >= r.x - 1
|
||||||
|
AND o.x <= r.x + 1
|
||||||
|
AND o.y >= r.y - 1
|
||||||
|
AND o.y <= r.y + 1
|
||||||
|
) < 4;
|
||||||
|
--- Create a scratch table where we're going to delete rolls from
|
||||||
|
CREATE TABLE rolls2(
|
||||||
|
x INTEGER NOT NULL,
|
||||||
|
y INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (x, y)
|
||||||
|
);
|
||||||
|
INSERT INTO rolls2
|
||||||
|
SELECT *
|
||||||
|
FROM rolls;
|
||||||
|
-- Delete the outer rolls of paper 77 times. This turns out to be enough.
|
||||||
|
-- I tried really hard not to do this.
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
DELETE FROM rolls2
|
||||||
|
WHERE (
|
||||||
|
SELECT COUNT(*) - 1
|
||||||
|
FROM rolls2 o
|
||||||
|
WHERE o.x >= rolls2.x - 1
|
||||||
|
AND o.x <= rolls2.x + 1
|
||||||
|
AND o.y >= rolls2.y - 1
|
||||||
|
AND o.y <= rolls2.y + 1
|
||||||
|
) < 4;
|
||||||
|
-- See how many we've deleted
|
||||||
|
SELECT (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM rolls
|
||||||
|
) - (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM rolls2
|
||||||
|
) as part2;
|
||||||
1
2025/day05/.gitignore
vendored
Normal file
1
2025/day05/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
solve
|
||||||
6
2025/day05/Makefile
Normal file
6
2025/day05/Makefile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CFLAGS=-Wall -Wextra -O2 -g
|
||||||
|
|
||||||
|
all: solve
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) solve
|
||||||
12
2025/day05/README.md
Normal file
12
2025/day05/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Day 05: C
|
||||||
|
|
||||||
|
C is weird. It's a very simple language but it's made complicated by the fact that you have to do
|
||||||
|
everything yourself. Relatively straightforward solution.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make
|
||||||
|
cc -Wall -Wextra -O2 -g solve.c -o solve
|
||||||
|
$ ./solve sample.txt
|
||||||
|
Part1: 3
|
||||||
|
Part2: 14
|
||||||
|
```
|
||||||
11
2025/day05/sample.txt
Normal file
11
2025/day05/sample.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
3-5
|
||||||
|
10-14
|
||||||
|
16-20
|
||||||
|
12-18
|
||||||
|
|
||||||
|
1
|
||||||
|
5
|
||||||
|
8
|
||||||
|
11
|
||||||
|
17
|
||||||
|
32
|
||||||
160
2025/day05/solve.c
Normal file
160
2025/day05/solve.c
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX(a,b) \
|
||||||
|
({ __typeof__ (a) _a = (a); \
|
||||||
|
__typeof__ (b) _b = (b); \
|
||||||
|
_a > _b ? _a : _b; })
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
} range_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t length;
|
||||||
|
size_t capacity;
|
||||||
|
range_t* buffer;
|
||||||
|
} ranges_t;
|
||||||
|
|
||||||
|
static int cmp_range(void const* va, void const* vb) {
|
||||||
|
range_t const* a = va;
|
||||||
|
range_t const* b = vb;
|
||||||
|
|
||||||
|
if (a->start == b->start) {
|
||||||
|
return (a->end > b->end) - (a->end < b->end);
|
||||||
|
} else {
|
||||||
|
return (a->start > b->start) - (a->start < b-> start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void simplify_ranges(ranges_t* ranges) {
|
||||||
|
// First, sort ranges ascending by start (then end)
|
||||||
|
qsort(ranges->buffer, ranges->length, sizeof(range_t), cmp_range);
|
||||||
|
|
||||||
|
const size_t length = ranges->length;
|
||||||
|
range_t* write_ptr = &ranges->buffer[0];
|
||||||
|
|
||||||
|
// Important, skip the first iteration, it can never be merged
|
||||||
|
for (size_t i = 1; i < length; ++i) {
|
||||||
|
if (ranges->buffer[i].start <= write_ptr->end) {
|
||||||
|
// Merge the two ranges
|
||||||
|
write_ptr->end = MAX(write_ptr->end, ranges->buffer[i].end);
|
||||||
|
ranges->length--;
|
||||||
|
} else {
|
||||||
|
write_ptr++;
|
||||||
|
if (write_ptr != &ranges->buffer[i]) {
|
||||||
|
memcpy(write_ptr, &ranges->buffer[i], sizeof(range_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for (size_t i = 0; i < ranges->length; ++i) {
|
||||||
|
// printf("%" PRIu64 "-%" PRIu64 "\n", ranges->buffer[i].start, ranges->buffer[i].end);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// printf("Original length: %u, new length: %u\n", (unsigned int) length, (unsigned int) ranges->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_fresh(const uint64_t ingredient, ranges_t const * const ranges) {
|
||||||
|
size_t min = 0;
|
||||||
|
size_t size = ranges->length;
|
||||||
|
range_t const* buffer = ranges->buffer;
|
||||||
|
|
||||||
|
while (size > 1) {
|
||||||
|
size_t half = size / 2;
|
||||||
|
size_t mid = min + half;
|
||||||
|
|
||||||
|
if (buffer[mid].start <= ingredient) {
|
||||||
|
min = mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= half;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[min].start <= ingredient && buffer[min].end >= ingredient;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, char const** argv) {
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("Usage: %s <input>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges_t ranges = {
|
||||||
|
.length = 0,
|
||||||
|
.capacity = 2,
|
||||||
|
.buffer = malloc(2 * sizeof(range_t)),
|
||||||
|
};
|
||||||
|
|
||||||
|
FILE* input = fopen(argv[1], "r");
|
||||||
|
if (input == NULL) {
|
||||||
|
perror("failed to open file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ingredient;
|
||||||
|
bool seen_first = false;
|
||||||
|
|
||||||
|
while (!seen_first) {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
|
||||||
|
switch (fscanf(input, "%" PRIu64 "-%" PRIu64, &start, &end)) {
|
||||||
|
case 1:
|
||||||
|
seen_first = true;
|
||||||
|
ingredient = start;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (ranges.capacity == ranges.length) {
|
||||||
|
size_t new_capacity = ranges.capacity * 2;
|
||||||
|
range_t* buffer = realloc(ranges.buffer, new_capacity * sizeof(range_t));
|
||||||
|
if (buffer == NULL) {
|
||||||
|
printf("Failed to resize ranges buffer\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.capacity = new_capacity;
|
||||||
|
ranges.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.buffer[ranges.length].start = start;
|
||||||
|
ranges.buffer[ranges.length].end = end;
|
||||||
|
ranges.length++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Unexpected end of file\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simplify_ranges(&ranges);
|
||||||
|
|
||||||
|
int total_fresh = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (is_fresh(ingredient, &ranges)) {
|
||||||
|
total_fresh++;
|
||||||
|
}
|
||||||
|
} while (fscanf(input, "%" PRIu64 "\n", &ingredient) == 1);
|
||||||
|
|
||||||
|
printf("Part1: %u\n", total_fresh);
|
||||||
|
|
||||||
|
uint64_t combined_fresh = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ranges.length; ++i) {
|
||||||
|
combined_fresh += ranges.buffer[i].end - ranges.buffer[i].start + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Part2: %" PRIu64 "\n", combined_fresh);
|
||||||
|
|
||||||
|
fclose(input);
|
||||||
|
free(ranges.buffer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
2025/day06/README.md
Normal file
10
2025/day06/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Day 06: PHP
|
||||||
|
|
||||||
|
Simple and straight-forward. This is a normal programming language.
|
||||||
|
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./solve.php sample.txt
|
||||||
|
Part1: 4277556
|
||||||
|
Part2: 3263827
|
||||||
|
```
|
||||||
4
2025/day06/sample.txt
Normal file
4
2025/day06/sample.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
123 328 51 64
|
||||||
|
45 64 387 23
|
||||||
|
6 98 215 314
|
||||||
|
* + * +
|
||||||
81
2025/day06/solve.php
Executable file
81
2025/day06/solve.php
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if ($argc < 2) {
|
||||||
|
echo "Usage: {$argv[0]} INPUT\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = file($argv[1]);
|
||||||
|
|
||||||
|
function part1(string ...$input): int {
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
foreach ($input as $line) {
|
||||||
|
$lines[] = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = array_reverse($lines);
|
||||||
|
$cols = count($lines[0]);
|
||||||
|
$rows = count($lines);
|
||||||
|
|
||||||
|
$sum = 0;
|
||||||
|
|
||||||
|
for ($col = 0; $col < $cols; ++$col) {
|
||||||
|
if ($lines[0][$col] === '+') {
|
||||||
|
$acc = 0;
|
||||||
|
for ($row = 1; $row < $rows; ++$row) {
|
||||||
|
$acc += (int) $lines[$row][$col];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$acc = 1;
|
||||||
|
for ($row = 1; $row < $rows; ++$row) {
|
||||||
|
$acc *= (int) $lines[$row][$col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum += $acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function part2(string ...$lines): int {
|
||||||
|
$cols = strlen($lines[0]);
|
||||||
|
$rows = count($lines);
|
||||||
|
$numbers = array_fill(0, $cols, "");
|
||||||
|
|
||||||
|
for ($row = 0; $row < $rows - 1; ++$row) {
|
||||||
|
foreach (str_split($lines[$row]) as $col => $c) {
|
||||||
|
if ($c !== ' ') {
|
||||||
|
$numbers[$col] .= $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = 0;
|
||||||
|
|
||||||
|
foreach (str_split($lines[$rows - 1]) as $col => $c) {
|
||||||
|
switch ($c) {
|
||||||
|
case '+':
|
||||||
|
$acc = 0;
|
||||||
|
for ($i = $col; $i < $cols && $numbers[$i] !== ""; ++$i) {
|
||||||
|
$acc += (int) $numbers[$i];
|
||||||
|
}
|
||||||
|
$sum += $acc;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
$acc = 1;
|
||||||
|
for ($i = $col; $i < $cols && $numbers[$i] !== ""; ++$i) {
|
||||||
|
$acc *= (int) $numbers[$i];
|
||||||
|
}
|
||||||
|
$sum += $acc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Part1: " . part1(...$input) . "\n";
|
||||||
|
echo "Part2: " . part2(...$input) . "\n";
|
||||||
3
2025/day07/.gitignore
vendored
Normal file
3
2025/day07/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
solve
|
||||||
|
*.o
|
||||||
|
*.hi
|
||||||
14
2025/day07/Makefile
Normal file
14
2025/day07/Makefile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.PHONY: all clean test
|
||||||
|
|
||||||
|
all: solve
|
||||||
|
|
||||||
|
test: solve
|
||||||
|
./solve sample.txt
|
||||||
|
|
||||||
|
solve: solve.hs
|
||||||
|
ghc -dynamic -O -g -o $@ $^
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) solve
|
||||||
|
$(RM) *.o
|
||||||
|
$(RM) *.hi
|
||||||
15
2025/day07/README.md
Normal file
15
2025/day07/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Day 07: Haskell
|
||||||
|
|
||||||
|
This one took me the longest, mostly because I really do not know Haskell. Nevertheless, I find the
|
||||||
|
resulting solution quite elegant. The `Makefile` I made uses dynamic linkage as that's how Haskell
|
||||||
|
works on Arch Linux. If you want to do static linking, `ghc solve.hs` should work.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make
|
||||||
|
ghc -dynamic -O -g -o solve solve.hs
|
||||||
|
[1 of 2] Compiling Main ( solve.hs, solve.o )
|
||||||
|
[2 of 2] Linking solve
|
||||||
|
$ ./solve sample.txt
|
||||||
|
21
|
||||||
|
40
|
||||||
|
```
|
||||||
16
2025/day07/sample.txt
Normal file
16
2025/day07/sample.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.......S.......
|
||||||
|
...............
|
||||||
|
.......^.......
|
||||||
|
...............
|
||||||
|
......^.^......
|
||||||
|
...............
|
||||||
|
.....^.^.^.....
|
||||||
|
...............
|
||||||
|
....^.^...^....
|
||||||
|
...............
|
||||||
|
...^.^...^.^...
|
||||||
|
...............
|
||||||
|
..^...^.....^..
|
||||||
|
...............
|
||||||
|
.^.^.^.^.^...^.
|
||||||
|
...............
|
||||||
65
2025/day07/solve.hs
Normal file
65
2025/day07/solve.hs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import Data.Array
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Environment
|
||||||
|
import System.Exit
|
||||||
|
|
||||||
|
main = getArgs >>= parse >>= run
|
||||||
|
|
||||||
|
stringToArray :: [Char] -> Array Int Char
|
||||||
|
stringToArray s = listArray (0, length s - 1) s
|
||||||
|
|
||||||
|
run :: String -> IO ()
|
||||||
|
run input =
|
||||||
|
let gridLines = lines input
|
||||||
|
startingPoint = findStartingPoint $ head gridLines
|
||||||
|
arrLines = Data.List.map stringToArray $ tail gridLines
|
||||||
|
in do
|
||||||
|
print $ part1 arrLines startingPoint
|
||||||
|
print $ part2 arrLines startingPoint
|
||||||
|
|
||||||
|
findStartingPoint :: [Char] -> Int
|
||||||
|
findStartingPoint line = fromJust $ elemIndex 'S' line
|
||||||
|
|
||||||
|
part1 arrLines startingPoint =
|
||||||
|
let initial = [startingPoint]
|
||||||
|
in simulate arrLines 0 initial
|
||||||
|
|
||||||
|
checkHit :: Array Int Char -> Int -> [Int]
|
||||||
|
checkHit line idx = case line ! idx of
|
||||||
|
'^' -> (idx - 1) : [idx + 1]
|
||||||
|
_ -> [idx]
|
||||||
|
|
||||||
|
simulate [] count active = count
|
||||||
|
simulate lines count active =
|
||||||
|
let arr = head lines
|
||||||
|
followUp = nub $ concatMap (checkHit arr) active
|
||||||
|
hits = length $ Data.List.filter (\i -> arr ! i == '^') active
|
||||||
|
remainder = tail lines
|
||||||
|
in simulate remainder (hits + count) followUp
|
||||||
|
|
||||||
|
part2 :: [Array Int Char] -> Int -> Int
|
||||||
|
part2 arrLines startingPosition =
|
||||||
|
let n = length arrLines
|
||||||
|
arr = listArray (0, n - 1) arrLines
|
||||||
|
width = length $ head arrLines
|
||||||
|
compute i pos
|
||||||
|
| i >= n = 1
|
||||||
|
| otherwise =
|
||||||
|
let line = arr ! i
|
||||||
|
in case line ! pos of
|
||||||
|
'^' -> memo ! (i + 1, pos - 1) + memo ! (i + 1, pos + 1)
|
||||||
|
_ -> memo ! (i + 1, pos)
|
||||||
|
memo =
|
||||||
|
array
|
||||||
|
((0, 0), (n, width - 1))
|
||||||
|
[((i, pos), compute i pos) | i <- [0 .. n], pos <- [0 .. width - 1]]
|
||||||
|
in memo ! (0, startingPosition)
|
||||||
|
|
||||||
|
parse ["-h"] = usage >> exitSuccess
|
||||||
|
parse [] = usage >> die usageStr
|
||||||
|
parse fs = concat `fmap` mapM readFile fs
|
||||||
|
|
||||||
|
usageStr = "Usage: solve [-vh] [file ..]"
|
||||||
|
|
||||||
|
usage = putStrLn usageStr
|
||||||
1
2025/day08/.gitignore
vendored
Normal file
1
2025/day08/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
solve
|
||||||
11
2025/day08/README.md
Normal file
11
2025/day08/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Day 08: Go
|
||||||
|
|
||||||
|
Nice puzzle, got to use an obscure data structure. And implement it, because Go doesn't have it in
|
||||||
|
its standard library.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go build
|
||||||
|
$ ./solve sample.txt 10
|
||||||
|
Part 1: 40
|
||||||
|
Part 2: 25272
|
||||||
|
```
|
||||||
3
2025/day08/go.mod
Normal file
3
2025/day08/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module bertptrs/solve/v2
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
20
2025/day08/sample.txt
Normal file
20
2025/day08/sample.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
162,817,812
|
||||||
|
57,618,57
|
||||||
|
906,360,560
|
||||||
|
592,479,940
|
||||||
|
352,342,300
|
||||||
|
466,668,158
|
||||||
|
542,29,236
|
||||||
|
431,825,988
|
||||||
|
739,650,466
|
||||||
|
52,470,668
|
||||||
|
216,146,977
|
||||||
|
819,987,18
|
||||||
|
117,168,530
|
||||||
|
805,96,715
|
||||||
|
346,949,466
|
||||||
|
970,615,88
|
||||||
|
941,993,340
|
||||||
|
862,61,35
|
||||||
|
984,92,344
|
||||||
|
425,690,689
|
||||||
192
2025/day08/solve.go
Normal file
192
2025/day08/solve.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"container/heap"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetEntry struct {
|
||||||
|
parent, size int
|
||||||
|
}
|
||||||
|
|
||||||
|
type DisjointSet struct {
|
||||||
|
set []SetEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDisjointSet(size int) *DisjointSet {
|
||||||
|
set := make([]SetEntry, size)
|
||||||
|
|
||||||
|
for i := range len(set) {
|
||||||
|
set[i].parent = i
|
||||||
|
set[i].size = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := new(DisjointSet)
|
||||||
|
instance.set = set
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DisjointSet) Find(item int) int {
|
||||||
|
for d.set[item].parent != item {
|
||||||
|
d.set[item].parent = d.set[d.set[item].parent].parent
|
||||||
|
item = d.set[item].parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DisjointSet) Union(x, y int) bool {
|
||||||
|
xp := d.Find(x)
|
||||||
|
yp := d.Find(y)
|
||||||
|
|
||||||
|
if xp == yp {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if yp < xp {
|
||||||
|
xp, yp = yp, xp
|
||||||
|
}
|
||||||
|
|
||||||
|
d.set[xp].size += d.set[yp].size
|
||||||
|
d.set[yp].parent = xp
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DisjointSet) Size(item int) int {
|
||||||
|
if d.set[item].parent == item {
|
||||||
|
return d.set[item].size
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DistanceHeap [][3]int
|
||||||
|
|
||||||
|
func (h DistanceHeap) Len() int { return len(h) }
|
||||||
|
func (h DistanceHeap) Less(i, j int) bool { return h[i][0] < h[j][0] }
|
||||||
|
func (h DistanceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
|
|
||||||
|
func (h *DistanceHeap) Push(x any) {
|
||||||
|
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||||
|
// not just its contents.
|
||||||
|
*h = append(*h, x.([3]int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DistanceHeap) Pop() any {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
x := old[n-1]
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func read_input(filename string) [][3]int {
|
||||||
|
var points [][3]int
|
||||||
|
var point [3]int
|
||||||
|
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
|
||||||
|
for {
|
||||||
|
parsed, err := fmt.Fscanf(reader, "%d,%d,%d\n", &point[0], &point[1], &point[2])
|
||||||
|
if err != nil || parsed != 3 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
points = append(points, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Printf("Usage: %v INPUT_FILE [connections]\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compute_group_sizes(groups *DisjointSet, len int) []int {
|
||||||
|
var sizes []int
|
||||||
|
for i := range len {
|
||||||
|
size := groups.Size(i)
|
||||||
|
if size > 0 {
|
||||||
|
sizes = append(sizes, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(sizes)
|
||||||
|
|
||||||
|
return sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
connections := 1000
|
||||||
|
if len(os.Args) >= 3 {
|
||||||
|
parsed, err := strconv.Atoi(os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
connections = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
points := read_input(os.Args[1])
|
||||||
|
|
||||||
|
distances := make([][3]int, 0, len(points)*(len(points)-1)/2)
|
||||||
|
|
||||||
|
for i, a := range points {
|
||||||
|
for j := i + 1; j < len(points); j += 1 {
|
||||||
|
b := points[j]
|
||||||
|
|
||||||
|
square_dist := (a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]) + (a[2]-b[2])*(a[2]-b[2])
|
||||||
|
|
||||||
|
distances = append(distances, [3]int{square_dist, i, j})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_heap := DistanceHeap(distances)
|
||||||
|
heap.Init(&size_heap)
|
||||||
|
|
||||||
|
groups := NewDisjointSet(len(points))
|
||||||
|
|
||||||
|
for range connections {
|
||||||
|
first := heap.Pop(&size_heap)
|
||||||
|
d := first.([3]int)
|
||||||
|
groups.Union(d[1], d[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes := compute_group_sizes(groups, len(points))
|
||||||
|
|
||||||
|
product := 1
|
||||||
|
for _, size := range sizes[len(sizes)-3:] {
|
||||||
|
product *= size
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Part 1: %v\n", product)
|
||||||
|
|
||||||
|
to_merge := len(sizes) - 1
|
||||||
|
for {
|
||||||
|
first := heap.Pop(&size_heap)
|
||||||
|
d := first.([3]int)
|
||||||
|
if groups.Union(d[1], d[2]) {
|
||||||
|
to_merge -= 1
|
||||||
|
if to_merge == 0 {
|
||||||
|
fmt.Printf("Part 2: %v\n", points[d[1]][0]*points[d[2]][0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
2025/day09/.gitignore
vendored
Normal file
1
2025/day09/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
14
2025/day09/README.md
Normal file
14
2025/day09/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Day 9: Typescript
|
||||||
|
|
||||||
|
Lost a lot of time on a swapped argument order. Oh well, such is life. The `package.json` exists to
|
||||||
|
instruct the interpreter on how to execute the file and doesn't otherwise include any meaningful
|
||||||
|
dependencies. Everything works with the standard library for Node.
|
||||||
|
|
||||||
|
`ts-node` is used for just-in-time Typescript compilation. You can also compile the file manually
|
||||||
|
first, then run it as JS. Also, Oracle, please release the name Javascript.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./solve.ts sample.txt
|
||||||
|
Part 1: 50
|
||||||
|
Part 2: 24
|
||||||
|
```
|
||||||
29
2025/day09/package-lock.json
generated
Normal file
29
2025/day09/package-lock.json
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "day09",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz",
|
||||||
|
"integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
2025/day09/package.json
Normal file
6
2025/day09/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
2025/day09/sample.txt
Normal file
8
2025/day09/sample.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
7,1
|
||||||
|
11,1
|
||||||
|
11,7
|
||||||
|
9,7
|
||||||
|
9,5
|
||||||
|
2,5
|
||||||
|
2,3
|
||||||
|
7,3
|
||||||
78
2025/day09/solve.ts
Executable file
78
2025/day09/solve.ts
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env ts-node
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { exit } from 'process';
|
||||||
|
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
console.log("Usage: " + process.argv0 + " " + process.argv[1] + " INPUT_FILE");
|
||||||
|
exit(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
const input_file = fs.readFileSync(process.argv[2], "utf-8");
|
||||||
|
const lines = input_file.trim().split("\n");
|
||||||
|
|
||||||
|
class Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
constructor(x: number, y: number) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = lines.map(line => {
|
||||||
|
const [x, y] = line.split(",");
|
||||||
|
return new Point(+x, +y);
|
||||||
|
});
|
||||||
|
|
||||||
|
let max_size = 0;
|
||||||
|
let max_size_contained = 0;
|
||||||
|
|
||||||
|
function has_intersection(left: number, right: number, bottom: number, top: number): boolean {
|
||||||
|
for (let i = 0; i < points.length; ++i) {
|
||||||
|
const first = points[i];
|
||||||
|
const second = points[(i + 1) % points.length];
|
||||||
|
|
||||||
|
if (first.x == second.x) {
|
||||||
|
const yMin = Math.min(first.y, second.y);
|
||||||
|
const yMax = Math.max(first.y, second.y);
|
||||||
|
|
||||||
|
if (left < first.x && first.x < right && (yMin <= bottom && bottom < yMax || yMin < top && top <= yMax)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (first.y == second.y) {
|
||||||
|
const xMin = Math.min(first.x, second.x);
|
||||||
|
const xMax = Math.max(first.x, second.x);
|
||||||
|
|
||||||
|
if (bottom < first.y && first.y < top && (xMin <= left && left < xMax || xMin < right && right <= xMax)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "Invalid input";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length; ++i) {
|
||||||
|
for (let j = i + 1; j < points.length; ++j) {
|
||||||
|
const left = Math.min(points[i].x, points[j].x);
|
||||||
|
const right = Math.max(points[i].x, points[j].x);
|
||||||
|
const bottom = Math.min(points[i].y, points[j].y);
|
||||||
|
const top = Math.max(points[i].y, points[j].y);
|
||||||
|
|
||||||
|
const width = right - left + 1;
|
||||||
|
const height = top - bottom + 1;
|
||||||
|
|
||||||
|
const area = width * height;
|
||||||
|
|
||||||
|
max_size = Math.max(max_size, area);
|
||||||
|
if (area > max_size_contained && !has_intersection(left, right, bottom, top)) {
|
||||||
|
max_size_contained = area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Part 1:", max_size);
|
||||||
|
// Too high: 4531758980
|
||||||
|
console.log("Part 2:", max_size_contained);
|
||||||
1
2025/day10/.gitignore
vendored
Normal file
1
2025/day10/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
7
2025/day10/Cargo.toml
Normal file
7
2025/day10/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "solve"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
microlp = "0.2.11"
|
||||||
3
2025/day10/sample.txt
Normal file
3
2025/day10/sample.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
|
||||||
|
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
|
||||||
|
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}
|
||||||
141
2025/day10/src/main.rs
Normal file
141
2025/day10/src/main.rs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use microlp::ComparisonOp;
|
||||||
|
use microlp::LinearExpr;
|
||||||
|
use microlp::OptimizationDirection;
|
||||||
|
use microlp::Problem;
|
||||||
|
|
||||||
|
fn parse_line(line: &str) -> (u32, Vec<u32>, Vec<u8>) {
|
||||||
|
let mut buttons = vec![];
|
||||||
|
let mut target = 0;
|
||||||
|
|
||||||
|
let mut it = line.chars();
|
||||||
|
|
||||||
|
for (i, c) in it.by_ref().skip(1).enumerate() {
|
||||||
|
match c {
|
||||||
|
'#' => target |= 1 << i,
|
||||||
|
'.' => (),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match it.nth(1) {
|
||||||
|
Some('{') => break,
|
||||||
|
Some('(') => (),
|
||||||
|
other => panic!("Unexpected character \"{other:?}\" in: {line}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut button = 0;
|
||||||
|
|
||||||
|
while let Some(c) = it.next() {
|
||||||
|
let d = c.to_digit(10).unwrap();
|
||||||
|
button |= 1 << d;
|
||||||
|
|
||||||
|
if let Some(')') = it.next() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rem = it.as_str().trim().trim_end_matches('}');
|
||||||
|
|
||||||
|
let joltage = rem.split(',').map(|j| j.parse().unwrap()).collect();
|
||||||
|
|
||||||
|
(target, buttons, joltage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_joltage(buttons: &[u32], joltage: &[u8]) -> i32 {
|
||||||
|
let mut problem = Problem::new(OptimizationDirection::Minimize);
|
||||||
|
let max = i32::from(*joltage.iter().max().unwrap_or(&0));
|
||||||
|
|
||||||
|
let variables: Vec<_> = buttons
|
||||||
|
.iter()
|
||||||
|
.map(|_| problem.add_integer_var(1.0, (0, max)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (bit, &value) in joltage.iter().enumerate() {
|
||||||
|
let mut equation = LinearExpr::empty();
|
||||||
|
|
||||||
|
for (&button, &variable) in buttons.iter().zip(&variables) {
|
||||||
|
if button & (1 << bit) != 0 {
|
||||||
|
equation.add(variable, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
problem.add_constraint(equation, ComparisonOp::Eq, value.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
problem.solve().unwrap().objective().round() as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minimum_clicks(target: u32, buttons: &[u32]) -> i32 {
|
||||||
|
let max = buttons
|
||||||
|
.iter()
|
||||||
|
.map(|s| 32 - s.leading_zeros())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let possible = 2 << max;
|
||||||
|
let mut seen = vec![false; possible];
|
||||||
|
let mut todo = VecDeque::new();
|
||||||
|
todo.push_back((0, 0));
|
||||||
|
|
||||||
|
while let Some((steps, state)) = todo.pop_front() {
|
||||||
|
for &button in buttons {
|
||||||
|
let next = state ^ button;
|
||||||
|
|
||||||
|
if next == target {
|
||||||
|
return steps + 1;
|
||||||
|
} else if !seen[next as usize] {
|
||||||
|
seen[next as usize] = true;
|
||||||
|
todo.push_back((steps + 1, next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("Did not find target");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve(input: &str) -> (i32, i32) {
|
||||||
|
let mut total_clicks = 0;
|
||||||
|
let mut total_presses = 0;
|
||||||
|
for line in input.trim().lines() {
|
||||||
|
let (target, buttons, joltage) = parse_line(line);
|
||||||
|
total_clicks += minimum_clicks(target, &buttons);
|
||||||
|
total_presses += min_joltage(&buttons, &joltage)
|
||||||
|
}
|
||||||
|
|
||||||
|
(total_clicks, total_presses)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
if let Some(path) = env::args_os().nth(1) {
|
||||||
|
let input = fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
let (part1, part2) = solve(&input);
|
||||||
|
println!("Part 1: {part1}\nPart 2: {part2}");
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
eprintln!("Usage: {} INPUT_FILE", env::args().next().unwrap());
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE: &str = include_str!("../sample.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sample() {
|
||||||
|
let (part1, part2) = solve(SAMPLE);
|
||||||
|
assert_eq!(7, part1);
|
||||||
|
assert_eq!(33, part2);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
2025/day11/README.md
Normal file
10
2025/day11/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Day 11: Python
|
||||||
|
|
||||||
|
Straightforward. Uses `networkx` because I thought it would be very helpful but it only really saves
|
||||||
|
me from writing a topological sort algorithm in the end.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ uv run ./solve.py input.txt
|
||||||
|
Part 1: secret
|
||||||
|
Part 2: secret
|
||||||
|
```
|
||||||
9
2025/day11/pyproject.toml
Normal file
9
2025/day11/pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "solve"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"networkx>=3.6.1",
|
||||||
|
]
|
||||||
10
2025/day11/sample.txt
Normal file
10
2025/day11/sample.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
aaa: you hhh
|
||||||
|
you: bbb ccc
|
||||||
|
bbb: ddd eee
|
||||||
|
ccc: ddd eee fff
|
||||||
|
ddd: ggg
|
||||||
|
eee: out
|
||||||
|
fff: out
|
||||||
|
ggg: out
|
||||||
|
hhh: ccc fff iii
|
||||||
|
iii: out
|
||||||
14
2025/day11/sample2.txt
Normal file
14
2025/day11/sample2.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
svr: aaa bbb
|
||||||
|
aaa: fft
|
||||||
|
fft: ccc
|
||||||
|
bbb: tty
|
||||||
|
tty: ccc
|
||||||
|
ccc: ddd eee
|
||||||
|
ddd: hub
|
||||||
|
hub: fff
|
||||||
|
eee: dac
|
||||||
|
dac: fff
|
||||||
|
fff: ggg hhh
|
||||||
|
ggg: out
|
||||||
|
hhh: out
|
||||||
|
you: out
|
||||||
70
2025/day11/solve.py
Executable file
70
2025/day11/solve.py
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
import fileinput
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def parse_graph() -> nx.Graph:
|
||||||
|
graph = nx.DiGraph()
|
||||||
|
|
||||||
|
for line in fileinput.input():
|
||||||
|
source, rem = line.split(": ")
|
||||||
|
|
||||||
|
for sink in rem.strip().split(" "):
|
||||||
|
graph.add_edge(source, sink)
|
||||||
|
|
||||||
|
return graph
|
||||||
|
|
||||||
|
def count(gen: Iterable) -> int:
|
||||||
|
return sum(1 for _ in gen)
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
graph = parse_graph()
|
||||||
|
|
||||||
|
# Observation: graph is a DAG, so one needs to go in front of the other. We can do this in three
|
||||||
|
# steps:
|
||||||
|
# svr → closest(dac, fft) -> furthest(dac,fft)
|
||||||
|
rank = {
|
||||||
|
node: rank
|
||||||
|
for rank, node in enumerate(nx.topological_sort(graph))
|
||||||
|
}
|
||||||
|
|
||||||
|
rev_rank = {rank: node for node, rank in rank.items()}
|
||||||
|
|
||||||
|
if rank["dac"] > rank["fft"]:
|
||||||
|
closest = "fft"
|
||||||
|
furthest = "dac"
|
||||||
|
else:
|
||||||
|
closest = "dac"
|
||||||
|
furthest = "fft"
|
||||||
|
|
||||||
|
|
||||||
|
def ranked_all_paths(source: str, dest: str) -> int:
|
||||||
|
counts = defaultdict(int)
|
||||||
|
|
||||||
|
counts[dest] = 1
|
||||||
|
|
||||||
|
for r in range(rank[dest], rank[source], -1):
|
||||||
|
node = rev_rank[r]
|
||||||
|
if node not in counts:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for u, _ in graph.in_edges(node):
|
||||||
|
counts[u] += counts[node]
|
||||||
|
|
||||||
|
return counts[source]
|
||||||
|
|
||||||
|
assert nx.has_path(graph, closest, furthest)
|
||||||
|
|
||||||
|
print("Part 1", ranked_all_paths("you", "out"))
|
||||||
|
|
||||||
|
first = ranked_all_paths("svr", closest)
|
||||||
|
second = ranked_all_paths(closest, furthest)
|
||||||
|
third = ranked_all_paths(furthest, "out")
|
||||||
|
print("Part 2:", first * second * third)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user