Skip to content
Merged

2025 #23

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ members = [
"rust/y2024/d19",
"rust/y2024/d20",
"rust/y2024/d21",
"rust/y2024/d24",
"rust/y2024/d24", "rust/y2025/d01", "rust/y2025/d02", "rust/y2025/d03", "rust/y2025/d04", "rust/y2025/d05", "rust/y2025/d06", "rust/y2025/d07", "rust/y2025/d08", "rust/y2025/d09", "rust/y2025/d10", "rust/y2025/d11", "rust/y2025/d12",
]

[package]
Expand Down Expand Up @@ -57,3 +57,15 @@ y2024d21 = { path = "rust/y2024/d21" }
y2024d24 = { path = "rust/y2024/d24" }
y2024d16 = { path = "rust/y2024/d16" }
y2024d17 = { path = "rust/y2024/d17" }
y2025d01 = { path = "rust/y2025/d01" }
y2025d02 = { path = "rust/y2025/d02" }
y2025d03 = { path = "rust/y2025/d03" }
y2025d04 = { path = "rust/y2025/d04" }
y2025d05 = { path = "rust/y2025/d05" }
y2025d06 = { path = "rust/y2025/d06" }
y2025d07 = { path = "rust/y2025/d07" }
y2025d08 = { path = "rust/y2025/d08" }
y2025d09 = { path = "rust/y2025/d09" }
y2025d10 = { path = "rust/y2025/d10" }
y2025d11 = { path = "rust/y2025/d11" }
y2025d12 = { path = "rust/y2025/d12" }
28 changes: 28 additions & 0 deletions pyaoc/solutions/y2025/y2025d06.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from functools import reduce
from operator import mul


def part1(input_str: str) -> str:
*lines, ops_str = input_str.splitlines()
ops = map({"+": sum, "*": product}.__getitem__, ops_str.split())
nss = zip(*[map(int, s.split()) for s in lines])
result = sum(op(ns) for op, ns in zip(ops, nss))
return str(result)


def part2(input_str: str) -> str:
*lines, ops_str = input_str.splitlines()
ops = map({"+": sum, "*": product}.__getitem__, ops_str.split())
cols = ["".join(s) for s in zip(*lines)]
nss: list[list[int]] = [[]]
for c in cols:
try:
nss[-1].append(int(c))
except ValueError:
nss.append([])
result = sum(op(ns) for op, ns in zip(ops, nss))
return str(result)


def product(xs):
return reduce(mul, xs)
44 changes: 44 additions & 0 deletions pyaoc/solutions/y2025/y2025d10.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import numpy as np # type: ignore[import-not-found]
from scipy import optimize # type: ignore[import-not-found]


def part1(input_str: str) -> str:
raise NotImplementedError


def part2(input_str: str) -> str:
machines = parse_input(input_str)
tot = sum(m.find_joltage_presses() for m in machines)
return str(tot)


class Machine:
def __init__(self, buttons: list[list[int]], joltage: list[int]):
self.buttons = buttons
self.joltage = joltage

@classmethod
def from_str(cls, s: str) -> "Machine":
(_, *buttons_str, joltage_str) = s.split(" ")
buttons = [list(map(int, s.strip("()").split(","))) for s in buttons_str]
joltage = list(
map(int, joltage_str.removeprefix("{").removesuffix("}").split(","))
)
return Machine(buttons, joltage)

def find_joltage_presses(self) -> int:
n_vars = len(self.buttons)
b = np.array(
[[i in b for i in range(len(self.joltage))] for b in self.buttons]
).T
j = np.array(self.joltage)
res = optimize.milp(
c=np.ones(n_vars),
constraints=optimize.LinearConstraint(b, lb=j, ub=j),
integrality=1,
)
return int(res.fun)


def parse_input(input_str: str) -> list[Machine]:
return list(map(Machine.from_str, input_str.splitlines()))
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies = [
"networkx>=3.4.2",
"pre-commit>=4.0.1",
"ruff>=0.7.4",
"scipy>=1.16.3",
"tqdm>=4.67.0",
]

Expand Down
7 changes: 7 additions & 0 deletions rust/y2025/d01/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "y2025d01"
version = "0.1.0"
edition = "2021"

[lib]
path = "y2025d01.rs"
58 changes: 58 additions & 0 deletions rust/y2025/d01/y2025d01.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
pub fn part1(input: String) -> Result<String, Box<dyn std::error::Error>> {
let mut dial = Dial::new();
for instruction in input.lines() {
dial.rotate(parse_instruction(instruction)?)?;
}
Ok(dial.count_final.to_string())
}

pub fn part2(input: String) -> Result<String, Box<dyn std::error::Error>> {
let mut dial = Dial::new();
for instruction in input.lines() {
dial.rotate(parse_instruction(instruction)?)?;
}
Ok(dial.count_pass.to_string())
}

fn parse_instruction(instruction: &str) -> Result<i64, Box<dyn std::error::Error>> {
let mut chars = instruction.chars();
let sign = match chars.next() {
Some('R') => 1,
Some('L') => -1,
_ => return Err("Invalid instruction".into()),
};
let amount: i64 = chars.collect::<String>().parse()?;
Ok(sign * amount)
}

struct Dial {
position: i64,
count_final: u64,
count_pass: u64,
}

impl Dial {
fn new() -> Self {
Dial {
position: 50,
count_final: 0,
count_pass: 0,
}
}

fn rotate(&mut self, amount: i64) -> Result<(), Box<dyn std::error::Error>> {
let new = self.position + amount;
self.count_pass += u64::try_from((new / 100).abs())?;

if new <= 0 && self.position > 0 {
// Passed zero turning left
self.count_pass += 1;
}

self.position = new.rem_euclid(100);
if self.position == 0 {
self.count_final += 1
}
Ok(())
}
}
7 changes: 7 additions & 0 deletions rust/y2025/d02/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "y2025d02"
version = "0.1.0"
edition = "2021"

[lib]
path = "y2025d02.rs"
47 changes: 47 additions & 0 deletions rust/y2025/d02/y2025d02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub fn part1(input: String) -> Result<String, Box<dyn std::error::Error>> {
let ranges = parse_input(input)?;
let total: u64 = ranges
.into_iter()
.flat_map(|(a, b)| (a..=b).filter(is_invalid_1))
.sum();
Ok(total.to_string())
}

pub fn part2(input: String) -> Result<String, Box<dyn std::error::Error>> {
let ranges = parse_input(input)?;
let total: u64 = ranges
.into_iter()
.flat_map(|(a, b)| (a..=b).filter(is_invalid_2))
.sum();
Ok(total.to_string())
}

fn parse_input(input: String) -> Result<Vec<(u64, u64)>, Box<dyn std::error::Error>> {
input
.trim()
.split(',')
.map(|r| {
let (a, b) = r.split_once('-').ok_or("missing -")?;
Ok((a.parse()?, b.parse()?))
})
.collect()
}

fn is_invalid_1(n: &u64) -> bool {
// must have an even number of digits
// and be a multiple of 10..01 = 10^(nd/2) + 1
let nd = n.ilog10() + 1;
nd.is_multiple_of(2) && n.is_multiple_of(10u64.pow(nd / 2) + 1)
}

fn is_invalid_2(n: &u64) -> bool {
// iterate over the possible factorizations of number of digits
// into number of repetitions * number of repeated digits
let nd = n.ilog10() + 1;
(2..=nd)
.filter(|nrep| nd.is_multiple_of(*nrep))
.any(|nrep| {
let rep_factor = (0..nrep).map(|i| 10u64.pow(nd / nrep * i)).sum();
n.is_multiple_of(rep_factor)
})
}
7 changes: 7 additions & 0 deletions rust/y2025/d03/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "y2025d03"
version = "0.1.0"
edition = "2021"

[lib]
path = "y2025d03.rs"
38 changes: 38 additions & 0 deletions rust/y2025/d03/y2025d03.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
pub fn part1(input: String) -> Result<String, Box<dyn std::error::Error>> {
solve(input, 2)
}

pub fn part2(input: String) -> Result<String, Box<dyn std::error::Error>> {
solve(input, 12)
}

fn solve(input: String, n: usize) -> Result<String, Box<dyn std::error::Error>> {
let result: Result<u64, Box<dyn std::error::Error>> = input
.trim()
.lines()
.map(|r| max_joltage_n(r, n))
.collect::<Result<Vec<_>, _>>()
.map(|vec| vec.iter().sum());
Ok(result?.to_string())
}

fn max_joltage_n(row: &str, n: usize) -> Result<u64, Box<dyn std::error::Error>> {
if n == 1 {
return row
.chars()
.max()
.and_then(|d| d.to_digit(10))
.map(|d| d as u64)
.ok_or("Invalid bank".into());
}
let (i, first_digit) = row[..row.len() - (n - 1)]
.char_indices()
.rev()
.max_by_key(|(_, c)| *c)
.ok_or("Bank is too small")?;

// Recursive is slow for long banks, consider iterating over the row only once.
let result = 10u64.pow((n - 1) as u32) * first_digit.to_digit(10).ok_or("Invalid bank")? as u64
+ max_joltage_n(&row[i + 1..], n - 1)?;
Ok(result)
}
7 changes: 7 additions & 0 deletions rust/y2025/d04/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "y2025d04"
version = "0.1.0"
edition = "2021"

[lib]
path = "y2025d04.rs"
Loading
Loading