23 Commits

Author SHA1 Message Date
bc6f3dc8c6 2025 day 10 part 2 in Rust
Using a library. I'm not happy about it but also I have thought about
this enough.
2025-12-11 21:46:15 +01:00
a8a1c85498 2025 day 11 in Python 2025-12-11 09:44:39 +01:00
ff8b8dac0c 2025 day 10 part 1 in Rust 2025-12-10 08:39:51 +01:00
51ca5db42e 2025 day 9 part 2 in TS 2025-12-09 19:44:31 +01:00
e60bfbf8c4 2025 day 9 part 1 in TypeScript 2025-12-09 08:41:13 +01:00
768a3ff10b One pointless index removed 2025-12-08 22:07:02 +01:00
c3d3116f8a Improvise heap sort for partial sorting 2025-12-08 21:51:17 +01:00
13592a60ee 2025 day 8 in Go 2025-12-08 21:21:24 +01:00
be6c3d37ea Minor cleanup
Avoid repeatedly creating sets that live for almost no time, as well as
some readability fixes
2025-12-07 22:38:39 +01:00
ff2c86a11b Missing compilation instructions 2025-12-07 22:26:25 +01:00
a737a35227 2025 day 7 part 2 in Haskell 2025-12-07 16:51:24 +01:00
29e02c819e Implement 2025 day 7 part 1 in Haskell 2025-12-07 15:12:21 +01:00
a01eb547d1 2025 day 6 in PHP 2025-12-06 12:42:32 +01:00
1485eb3cd5 Implement 2025 day 5 in C 2025-12-05 08:52:58 +01:00
0a7ec71a97 Very ugly part 2 2025-12-04 22:35:12 +01:00
f2204e554b Moderately cursed 2025 day 4 part 1 2025-12-04 09:29:39 +01:00
6f4b02af33 Implement 2025 day 3 in bash 2025-12-03 09:54:40 +01:00
7e23cf94a6 2025 day 2 part 2 in Terraform 2025-12-02 13:39:20 +01:00
f132842b5c Forgot to add 2025-12-02 09:58:16 +01:00
027a7bdde6 Attempt at part two, OOM 2025-12-02 09:55:37 +01:00
2277721010 2025 day 2 part 1 2025-12-02 09:36:44 +01:00
5e9a24c8d7 Document Nix solution 2025-12-01 21:13:50 +01:00
1468c87347 Implement 2025 day 1 part 2 2025-12-01 20:47:21 +01:00
47 changed files with 2189 additions and 14 deletions

17
2025/day01/README.md Normal file
View 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

View File

@@ -4,28 +4,34 @@
data = builtins.readFile input_file; data = builtins.readFile input_file;
lines = builtins.filter (s: s != "" && builtins.isString s) (builtins.split "\n" data); 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; mod = a: b: a - (a / b) * b;
abs = n: if n < 0 then -n else n;
recurse = list: position: recurse = list: position: score1: score2:
let let
first = builtins.head list; first = builtins.head list;
# This is not guaranteed to work but it does # This is not guaranteed to work but it's good enough
num_part = builtins.substring 1 999 first; num_part = builtins.substring 1 999 first;
num = builtins.fromJSON num_part; num = builtins.fromJSON num_part;
next = if (builtins.substring 0 1 first) == "R" delta = mod (if (builtins.substring 0 1 first) == "R" then num else -num) 100;
then next = mod (position + delta + 100) 100;
position + num
else
position - num;
new_position = mod ((mod next 100) + 100) 100; score = if next == 0 then 1 else 0;
circles = num / 100;
score = if new_position == 0 then 1 else 0; passed = if position == 0 then
in
if list == [] then
0 0
else else
score + (recurse (builtins.tail list) new_position); if delta < 0 && delta + position <= 0 || delta > 0 && delta + position >= 100 then
1
else
0;
in in
recurse lines 50; 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
987654321111111
811111111111119
234234234234278
818181911112111

64
2025/day03/solve.sh Executable file
View 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
View 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
View File

@@ -0,0 +1,10 @@
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.

759
2025/day04/solve.sql Normal file
View 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
View File

@@ -0,0 +1 @@
solve

6
2025/day05/Makefile Normal file
View File

@@ -0,0 +1,6 @@
CFLAGS=-Wall -Wextra -O2 -g
all: solve
clean:
$(RM) solve

12
2025/day05/README.md Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
123 328 51 64
45 64 387 23
6 98 215 314
* + * +

81
2025/day06/solve.php Executable file
View 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
View File

@@ -0,0 +1,3 @@
solve
*.o
*.hi

14
2025/day07/Makefile Normal file
View 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
View 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
View File

@@ -0,0 +1,16 @@
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

65
2025/day07/solve.hs Normal file
View 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
View File

@@ -0,0 +1 @@
solve

11
2025/day08/README.md Normal file
View 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
View File

@@ -0,0 +1,3 @@
module bertptrs/solve/v2
go 1.25.5

20
2025/day08/sample.txt Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
node_modules

14
2025/day09/README.md Normal file
View 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
View 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
View File

@@ -0,0 +1,6 @@
{
"type": "module",
"devDependencies": {
"@types/node": "^24.10.2"
}
}

8
2025/day09/sample.txt Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
target/

7
2025/day10/Cargo.toml Normal file
View 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
View 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
View 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
View 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
```

View 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
View 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
View 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
View 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()