Skip to content

Commit 901e860

Browse files
committedDec 8, 2023
Day 6, part 1: Back to highschool.
1 parent 86973d6 commit 901e860

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed
 

‎day6.input

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Time: 46 80 78 66
2+
Distance: 214 1177 1402 1024

‎src/day6.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
struct RaceTable {
2+
races: Vec<(usize, usize)>,
3+
}
4+
5+
impl std::str::FromStr for RaceTable {
6+
type Err = &'static str;
7+
8+
fn from_str(s: &str) -> Result<Self, Self::Err> {
9+
if let Some((times_line, distances_line)) = s.split_once('\n') {
10+
let times = times_line.split_ascii_whitespace().skip(1).map(|s| s.parse::<usize>()).collect::<Result<Vec<usize>, _>>().map_err(|_| "cannot parse times")?;
11+
let distances = distances_line.split_ascii_whitespace().skip(1).map(|s| s.parse::<usize>()).collect::<Result<Vec<usize>, _>>().map_err(|_| "cannot parse distances")?;
12+
if times.len() != distances.len() {
13+
return Err("mismatch between times and distances")
14+
}
15+
return Ok(RaceTable { races: times.iter().zip(distances.iter()).map(|(time, distance)| (*time, *distance)).collect() })
16+
}
17+
Err("cannot parse")
18+
}
19+
}
20+
21+
impl RaceTable {
22+
fn number_of_ways_to_beat_the_record(time: usize, distance: usize) -> usize {
23+
// The distance is the record to beat, and we do that by by checking for each possible
24+
// time of holding the button (x) the value of x * (time - x) > distance.
25+
// Instead of running over the values, we can also find the two zeros of the function -x^2 - x*time - distance = 0: The number of
26+
// times we beat the record is then floor(x2 - x1). We know that x1 is >= 0; there is a chance
27+
// that x2 is > time, in which case we have to use time instead of x2.
28+
//
29+
// -x^2 + time * x - distance = 0 | * -1
30+
// x^2 - time * x + distance = 0
31+
//
32+
// The zeros of the function are x{1,2} = time/2 +- sqrt(time^2/4 - distance).
33+
//
34+
// The tricky part here is that if the zero is not an integer, we have to round it up (x1) or down (x2),
35+
// and if it is an integer we need to exclude it from the result and use the next higher/lower value.
36+
// We can do that by adding 1 and then rounding down (x1) or subtracting 1 and then rounding up (x2).
37+
let m = ((time * time / 4 - distance) as f32).sqrt();
38+
let x1 = ((time as f32) / 2.0 - m + 1_f32).floor().clamp(0_f32, time as f32);
39+
let x2 = ((time as f32) / 2.0 + m - 1_f32).ceil().clamp(0_f32, time as f32);
40+
let result = (x2 - x1 + 1_f32).floor() as usize;
41+
println!("time = {}, distance = {}, m = {}, x1 = {}, x2 = {}: result = {}", time, distance, m, x1, x2, result);
42+
result
43+
}
44+
45+
fn product(&self) -> usize {
46+
let mut result = 1;
47+
for (time, distance) in &self.races {
48+
let number_of_ways = Self::number_of_ways_to_beat_the_record(*time, *distance);
49+
result *= number_of_ways;
50+
}
51+
result
52+
}
53+
}
54+
55+
pub fn main() {
56+
match std::fs::read_to_string("day6.input") {
57+
Ok(input) => {
58+
if let Ok(race_table) = input.parse::<RaceTable>() {
59+
println!("product of number of ways to win races (part 1) = {}", race_table.product());
60+
}
61+
},
62+
Err(reason) => println!("error = {}", reason)
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use super::*;
69+
70+
#[test]
71+
fn test() {
72+
static DATA: &str = "Time: 7 15 30
73+
Distance: 9 40 200";
74+
assert_eq!(DATA.parse::<RaceTable>().ok().unwrap().product(), 288);
75+
}
76+
}

‎src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
mod day5;
1+
mod day6;
22

33
fn main() {
4-
day5::main()
4+
day6::main()
55
}

0 commit comments

Comments
 (0)
Please sign in to comment.