Skip to content

Commit 2307de9

Browse files
committed
Day 7
1 parent 6014989 commit 2307de9

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

src/day07.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::{fs, iter::zip, num::ParseIntError, path::PathBuf, str::FromStr};
2+
3+
use itertools::Itertools;
4+
5+
pub fn day07(mut input_path: PathBuf) {
6+
input_path.push("07.txt");
7+
let content = fs::read_to_string(input_path).unwrap();
8+
9+
let equations: Vec<Equation> = content.lines().map(|l| l.parse().unwrap()).collect();
10+
11+
println!("{:?}", part1(&equations));
12+
println!("{:?}", part2(&equations));
13+
}
14+
15+
#[derive(Debug)]
16+
struct Equation {
17+
result: u64,
18+
terms: Vec<u64>,
19+
}
20+
21+
#[derive(Debug, Clone, Copy)]
22+
enum Operator {
23+
Add,
24+
Multiply,
25+
Concat,
26+
}
27+
28+
fn part1(equations: &[Equation]) -> u64 {
29+
equations
30+
.iter()
31+
.filter(|e| can_be_true(e, vec![Operator::Add, Operator::Multiply]))
32+
.map(|e| e.result)
33+
.sum()
34+
}
35+
36+
fn part2(equations: &[Equation]) -> u64 {
37+
equations
38+
.iter()
39+
.filter(|e| can_be_true(e, vec![Operator::Add, Operator::Multiply, Operator::Concat]))
40+
.map(|e| e.result)
41+
.sum()
42+
}
43+
44+
fn eval(a: u64, op: Operator, b: u64) -> u64 {
45+
match op {
46+
Operator::Add => a + b,
47+
Operator::Multiply => a * b,
48+
Operator::Concat => {
49+
let mut o = 1;
50+
while o <= b {
51+
o *= 10;
52+
}
53+
a * o + b
54+
}
55+
}
56+
}
57+
58+
fn is_true(equation: &Equation, operators: &[Operator]) -> bool {
59+
let [init, tail @ ..] = equation.terms.as_slice() else {
60+
// we checked during parsing
61+
panic!("RHS needs at least one term");
62+
};
63+
64+
equation.result == zip(operators, tail).fold(*init, |acc, (&op, &t)| eval(acc, op, t))
65+
66+
// Early stopping the fold when we exceed result is surprisingly not appreciably faster.
67+
// this try_fold is similar to (sum . filter ((>) result) . (scan foldop))
68+
69+
// zip(operators, tail)
70+
// .try_fold(*init, |acc, (&op, &t)| {
71+
// let s = eval(acc, op, t);
72+
// if s > equation.result {
73+
// return Err(s);
74+
// }
75+
// Ok(s)
76+
// })
77+
// .is_ok_and(|lhs| lhs == equation.result)
78+
}
79+
80+
fn can_be_true(equation: &Equation, options: Vec<Operator>) -> bool {
81+
operator_combinations_for(&equation, options).any(|ops| is_true(&equation, &ops))
82+
}
83+
84+
fn operator_combinations_for(
85+
equation: &Equation,
86+
options: Vec<Operator>,
87+
) -> Combinations<Operator> {
88+
Combinations::new((equation.terms.len() - 1) as u32, options)
89+
}
90+
91+
struct Combinations<T> {
92+
// TODO: Don't need to own that
93+
options: Vec<T>,
94+
length: u32,
95+
current: usize,
96+
}
97+
98+
impl<T> Combinations<T> {
99+
fn new(length: u32, options: Vec<T>) -> Self {
100+
Combinations {
101+
options,
102+
length,
103+
current: 0,
104+
}
105+
}
106+
}
107+
108+
impl<T: Copy> Iterator for Combinations<T> {
109+
type Item = Vec<T>;
110+
111+
fn next(&mut self) -> Option<Self::Item> {
112+
let k = self.options.len();
113+
self.current += 1;
114+
if self.current > k.pow(self.length) {
115+
return None;
116+
}
117+
let mut v: Vec<T> = Vec::with_capacity(self.length as usize);
118+
let n = self.current - 1;
119+
for i in 0..self.length {
120+
// part 1 was easy, we could map bits to operation: n >> i & 1
121+
v.push(self.options[(n / k.pow(i)) % k]);
122+
}
123+
Some(v)
124+
}
125+
}
126+
127+
#[derive(Debug)]
128+
struct ParseEquationError;
129+
130+
// so we can use '?' on Result<_, ParseIntError> in a function returning Result<_, ParseEquationError>
131+
impl From<ParseIntError> for ParseEquationError {
132+
fn from(_: ParseIntError) -> Self {
133+
ParseEquationError
134+
}
135+
}
136+
137+
impl FromStr for Equation {
138+
type Err = ParseEquationError;
139+
140+
fn from_str(s: &str) -> Result<Self, Self::Err> {
141+
let Some((lhs, rhs)) = s.split(":").next_tuple() else {
142+
return Err(ParseEquationError);
143+
};
144+
let result = lhs.parse()?;
145+
let terms = rhs
146+
.split_whitespace()
147+
.map(|t| t.parse::<u64>())
148+
.collect::<Result<Vec<_>, _>>()?;
149+
match terms.len() {
150+
0 => Err(ParseEquationError),
151+
_ => Ok(Equation { result, terms }),
152+
}
153+
}
154+
}

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod day03;
77
mod day04;
88
mod day05;
99
mod day06;
10+
mod day07;
1011

1112
#[derive(Parser)]
1213
struct Args {
@@ -23,6 +24,7 @@ fn main() {
2324
3 => day03::day03(input),
2425
4 => day04::day04(input),
2526
5 => day05::day05(input),
26-
_ => day06::day06(input),
27+
6 => day06::day06(input),
28+
_ => day07::day07(input),
2729
}
2830
}

0 commit comments

Comments
 (0)