5 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
15 changed files with 401 additions and 0 deletions

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