use std::{
fs::File,
io::{BufRead, BufReader},
path::Path,
str::FromStr,
};
pub fn solve(input: &Path) -> anyhow::Result<()> {
println!("part one: {}", part_one(input)?);
println!("part two: {}", part_two(input)?);
Ok(())
}
#[derive(Debug)]
struct Equation {
answer: u64,
inputs: Vec<u64>,
}
impl FromStr for Equation {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split(":");
Ok(Equation {
answer: parts.next().unwrap().parse()?,
inputs: parts
.next()
.unwrap()
.split_whitespace()
.map(|n| -> u64 { n.parse().unwrap() })
.collect(),
})
}
}
fn part_one(input: &Path) -> anyhow::Result<u64> {
let reader = BufReader::new(File::open(input)?);
let mut equations = vec![];
for line in reader.lines() {
equations.push(Equation::from_str(&line?).unwrap());
}
Ok(equations
.iter()
.filter(|eq| check_equation_one(eq))
.map(|eq| eq.answer)
.collect::<Vec<u64>>()
.iter()
.sum())
}
fn check_equation_one(eq: &Equation) -> bool {
let positions = eq.inputs.len() - 1;
if positions == 1 {
if eq.inputs[0] + eq.inputs[1] == eq.answer {
return true;
}
if eq.inputs[0] * eq.inputs[1] == eq.answer {
return true;
}
return false;
}
let operators = ["+", "*"];
let combinations: Vec<String> = (operators.len()..positions).fold(
operators
.iter()
.flat_map(|c| operators.iter().map(move |&d| d.to_owned() + *c))
.collect(),
|a, _| {
a.into_iter()
.flat_map(|c| operators.iter().map(move |&d| d.to_owned() + &*c))
.collect()
},
);
for combination in combinations {
let combination: Vec<char> = combination.chars().collect();
let mut current: Option<u64> = None;
for (i, input) in eq.inputs.iter().enumerate() {
if current.is_none() {
current = Some(*input);
continue;
}
match combination[i - 1] {
'+' => current = Some(current.unwrap() + input),
'*' => current = Some(current.unwrap() * input),
_ => panic!(),
}
}
if current.unwrap() == eq.answer {
return true;
}
}
false
}
fn part_two(input: &Path) -> anyhow::Result<u64> {
let reader = BufReader::new(File::open(input)?);
let mut equations = vec![];
for line in reader.lines() {
equations.push(Equation::from_str(&line?).unwrap());
}
Ok(equations
.iter()
.filter(|eq| check_equation_two(eq))
.map(|eq| eq.answer)
.collect::<Vec<u64>>()
.iter()
.sum())
}
fn check_equation_two(eq: &Equation) -> bool {
let positions = eq.inputs.len() - 1;
if positions == 1 {
if eq.inputs[0] + eq.inputs[1] == eq.answer {
return true;
}
if eq.inputs[0] * eq.inputs[1] == eq.answer {
return true;
}
if (eq.inputs[0].to_string() + &eq.inputs[1].to_string())
.parse::<u64>()
.unwrap()
== eq.answer
{
return true;
}
return false;
}
let operators = ["+", "*", "|"];
let combinations: Vec<String> = (operators.len()..positions + 1).fold(
operators
.iter()
.flat_map(|c| operators.iter().map(move |&d| d.to_owned() + *c))
.collect(),
|a, _| {
a.into_iter()
.flat_map(|c| operators.iter().map(move |&d| d.to_owned() + &*c))
.collect()
},
);
for combination in combinations {
let combination: Vec<char> = combination.chars().collect();
let mut current: Option<u64> = None;
for (i, input) in eq.inputs.iter().enumerate() {
if current.is_none() {
current = Some(*input);
continue;
}
match combination[i - 1] {
'+' => current = Some(current.unwrap() + input),
'*' => current = Some(current.unwrap() * input),
'|' => {
current = Some(
(current.unwrap().to_string() + &input.to_string())
.parse()
.unwrap(),
)
}
_ => panic!(),
}
}
if current.unwrap() == eq.answer {
return true;
}
}
false
}