This commit is contained in:
Gabe Venberg 2023-12-06 14:04:21 -06:00
parent 8bd03989a8
commit e1d0849bba
7 changed files with 238 additions and 0 deletions

8
Cargo.lock generated
View file

@ -55,6 +55,14 @@ dependencies = [
"nom", "nom",
] ]
[[package]]
name = "day06"
version = "0.1.0"
dependencies = [
"aoc_libs",
"nom",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.4" version = "2.6.4"

10
days/day06/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "day06"
authors.workspace = true
description.workspace = true
version.workspace = true
edition.workspace = true
[dependencies]
aoc_libs.workspace = true
nom.workspace=true

2
days/day06/src/input.txt Normal file
View file

@ -0,0 +1,2 @@
Time: 40 81 77 72
Distance: 219 1012 1365 1089

15
days/day06/src/main.rs Normal file
View file

@ -0,0 +1,15 @@
mod part1;
mod part2;
mod parse;
fn main() {
let input = include_str!("./input.txt");
let structured_input = parse::parse(input);
println!("Part One");
println!("Result: {}", part1::part1(&structured_input));
let structured_input = parse::part2_parse(input);
println!("Part Two");
println!("Result: {}", part2::part2(structured_input));
}

161
days/day06/src/parse.rs Normal file
View file

@ -0,0 +1,161 @@
use nom::{
bytes::complete::tag,
character::complete::multispace0,
multi::separated_list1,
sequence::{preceded, terminated},
IResult,
};
#[derive(Debug, PartialEq, Eq)]
pub struct Race {
pub time: u64,
pub record: u64,
}
impl Race {
pub fn distance_given_charge_time(&self, charge_time: u64) -> u64 {
if charge_time <= self.time {
charge_time * (self.time - charge_time)
} else {
0
}
}
pub fn num_ways_to_win(&self) -> u64 {
// since distance = charge(time-charge),
// we can rearrange into charge^2-(time)charge + distance = 0.
// if we set distance to record+1 (the min distance needed to win), we can use the quadratic formula, where
// a=1
// also notice that the upper and lower bound of charge times always sums up to
// the total time, so we can compute the lower bound from the upper bound.
// (too lazy to prove this...)
let b = -(self.time as f64);
let c = (self.record + 1) as f64;
let upper_bound = ((-b + (b.powi(2) - 4.0 * c).sqrt()) / 2.0).floor() as u64;
let lower_bound = self.time - upper_bound;
println!(
"upper bound is {}, lower bound is {}",
upper_bound, lower_bound
);
// off by one because if your upper and lower bounds are the same, there is 1 way to win.
upper_bound - lower_bound + 1
}
}
pub fn parse(input: &str) -> Vec<Race> {
let times: IResult<&str, Vec<u64>> = terminated(
preceded(
preceded(tag("Time:"), multispace0),
separated_list1(multispace0, nom::character::complete::u64),
),
multispace0,
)(input);
let (input, times) = times.unwrap();
let distances: IResult<&str, Vec<u64>> = terminated(
preceded(
preceded(tag("Distance:"), multispace0),
separated_list1(multispace0, nom::character::complete::u64),
),
multispace0,
)(input);
let (input, distances) = distances.unwrap();
assert_eq!(input, "");
times
.into_iter()
.zip(distances)
.map(|r| Race {
time: r.0,
record: r.1,
})
.collect()
}
pub fn part2_parse(input: &str) -> Race {
let mut string = input.to_string();
string.retain(|c| c != ' ');
let time: IResult<&str, u64> = terminated(
preceded(tag("Time:"), nom::character::complete::u64),
multispace0,
)(&string);
let (input, time) = time.unwrap();
let distance: IResult<&str, u64> = terminated(
preceded(tag("Distance:"), nom::character::complete::u64),
multispace0,
)(input);
let (input, distance) = distance.unwrap();
assert_eq!(input, "");
Race {
time,
record: distance,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_ways_to_win() {
let input = Race { time: 7, record: 9 };
assert_eq!(input.num_ways_to_win(), 4);
let input = Race {
time: 15,
record: 40,
};
assert_eq!(input.num_ways_to_win(), 8);
let input = Race {
time: 30,
record: 200,
};
assert_eq!(input.num_ways_to_win(), 9);
let input = Race {
time: 71530,
record: 940200,
};
assert_eq!(input.num_ways_to_win(), 71503);
}
#[test]
fn test_distance_given_charge_time() {
let input = Race { time: 7, record: 9 };
assert_eq!(input.distance_given_charge_time(0), 0);
assert_eq!(input.distance_given_charge_time(1), 6);
assert_eq!(input.distance_given_charge_time(2), 10);
assert_eq!(input.distance_given_charge_time(3), 12);
assert_eq!(input.distance_given_charge_time(4), 12);
assert_eq!(input.distance_given_charge_time(5), 10);
assert_eq!(input.distance_given_charge_time(6), 6);
assert_eq!(input.distance_given_charge_time(7), 0);
}
#[test]
fn test_parse_part2() {
let input = concat!("Time: 7 15 30\n", "Distance: 9 40 200\n",);
assert_eq!(
part2_parse(input),
Race {
time: 71530,
record: 940200
}
);
}
#[test]
fn test_parse() {
let input = concat!("Time: 7 15 30\n", "Distance: 9 40 200\n",);
assert_eq!(
parse(input),
vec![
Race { time: 7, record: 9 },
Race {
time: 15,
record: 40
},
Race {
time: 30,
record: 200
},
]
);
}
}

26
days/day06/src/part1.rs Normal file
View file

@ -0,0 +1,26 @@
use crate::parse::*;
pub fn part1(input: &[Race]) -> u64 {
input.iter().map(|r| r.num_ways_to_win()).product()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part1() {
let input = vec![
Race { time: 7, record: 9 },
Race {
time: 15,
record: 40,
},
Race {
time: 30,
record: 200,
},
];
assert_eq!(part1(&input), 288);
}
}

16
days/day06/src/part2.rs Normal file
View file

@ -0,0 +1,16 @@
use crate::parse::*;
pub fn part2(input: Race) -> u64 {
input.num_ways_to_win()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part2() {
let input = Race{ time: 71530, record: 940200 };
assert_eq!(part2(input), 71503);
}
}