|
| 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 | +} |
0 commit comments