|
1 | | -//! Challenge 01: Balanced Meal Pair |
2 | | -use std::collections::HashMap; |
3 | | - |
| 1 | +//! Challenge 01: Research Lab Recorder |
| 2 | +use anyhow::Result; |
4 | 3 | use colored::Colorize; |
5 | 4 |
|
6 | | -use crate::utils::split_whitespace_and_parse; |
7 | | - |
8 | | -pub fn run(input: &str, output: Option<&str>) -> anyhow::Result<String> { |
9 | | - let lines: Vec<&str> = input.trim().lines().collect(); |
10 | | - let outputs = output.map(split_whitespace_and_parse::<u16>).transpose()?; |
| 5 | +use crate::utils::{VecPreview, split_whitespace_and_parse}; |
11 | 6 |
|
12 | | - anyhow::ensure!(lines.len() == 2, "Invalid input format"); |
| 7 | +pub fn run(input: &str, output: Option<&str>) -> Result<String> { |
| 8 | + let mut lines = input.lines(); |
| 9 | + let n: u16 = lines.next().unwrap().trim().parse()?; |
13 | 10 |
|
14 | | - let target = lines[0].parse()?; |
15 | | - let meals = split_whitespace_and_parse(lines[1])?; |
| 11 | + let readings: Vec<u16> = split_whitespace_and_parse(lines.next().unwrap())?; |
| 12 | + anyhow::ensure!(readings.len() == n.into(), "Input length does not match n"); |
16 | 13 |
|
17 | | - match find_balanced_meal_pair(&meals, target) { |
18 | | - Some((i, j)) => { |
19 | | - if let Some(outputs) = outputs { |
20 | | - anyhow::ensure!(outputs.len() == 2, "Invalid output format"); |
21 | | - anyhow::ensure!( |
22 | | - (i == outputs[0] && j == outputs[1]) || (i == outputs[1] && j == outputs[0]), |
23 | | - "Indices do not match: expected ({}, {}) got ({i}, {j})", |
24 | | - outputs[0], |
25 | | - outputs[1], |
26 | | - ); |
| 14 | + let result = next_higher_readings(&readings); |
27 | 15 |
|
28 | | - return Ok(format!("Pair matches outputs at indices ({}, {})", i, j)); |
29 | | - } |
30 | | - |
31 | | - Ok(format!( |
32 | | - "Pair found at indices ({i}, {j}), but {} output!", |
33 | | - "missing".red().bold() |
34 | | - )) |
35 | | - } |
36 | | - None => anyhow::bail!("No suitable pair found"), |
37 | | - } |
38 | | -} |
| 16 | + if let Some(output) = output.map(split_whitespace_and_parse::<u16>).transpose()? { |
| 17 | + anyhow::ensure!( |
| 18 | + result == output, |
| 19 | + "Output vector does not match expected: got '{:?}' expected '{:?}'", |
| 20 | + result, |
| 21 | + output |
| 22 | + ); |
39 | 23 |
|
40 | | -fn find_balanced_meal_pair(meals: &[u16], target: u16) -> Option<(u16, u16)> { |
41 | | - if meals.len() < 2 { |
42 | | - return None; |
| 24 | + return Ok(format!( |
| 25 | + "Result matches the specified output: {}", |
| 26 | + result.preview(15) |
| 27 | + )); |
43 | 28 | } |
44 | 29 |
|
45 | | - let mut seen = HashMap::new(); |
46 | | - let mut best_pair = None::<(u16, u16)>; |
47 | | - let mut best_diff = u16::MAX; |
48 | | - |
49 | | - for (i, &meal) in meals.iter().enumerate() { |
50 | | - let (complement, overflowing) = target.overflowing_sub(meal); |
51 | | - |
52 | | - if overflowing { |
53 | | - continue; |
54 | | - } |
55 | | - |
56 | | - if let Some(&j) = seen.get(&complement) { |
57 | | - let diff = (meal + complement).abs_diff(target); |
58 | | - if diff < best_diff { |
59 | | - best_diff = diff; |
60 | | - best_pair = Some((j, i as u16)); |
61 | | - } |
62 | | - } |
| 30 | + Ok(format!( |
| 31 | + "Calculated the next higher reading of {result:?}, but {} output!", |
| 32 | + "missing".red().bold() |
| 33 | + )) |
| 34 | +} |
63 | 35 |
|
64 | | - for (&prev_meal, &prev_idx) in seen.iter() { |
65 | | - let sum: u16 = meal + prev_meal; |
66 | | - let diff = sum.abs_diff(target); |
67 | | - if diff < best_diff { |
68 | | - best_diff = diff; |
69 | | - best_pair = Some((prev_idx, i as u16)); |
| 36 | +pub fn next_higher_readings(readings: &[u16]) -> Vec<u16> { |
| 37 | + let n = readings.len(); |
| 38 | + let mut result = vec![0; n]; |
| 39 | + let mut stack: Vec<usize> = Vec::new(); |
| 40 | + |
| 41 | + for (i, &reading) in readings.iter().enumerate() { |
| 42 | + while let Some(&last) = stack.last() { |
| 43 | + if reading > readings[last] { |
| 44 | + result[last] = (i - last) as u16; |
| 45 | + stack.pop(); |
| 46 | + } else { |
| 47 | + break; |
70 | 48 | } |
71 | 49 | } |
72 | | - |
73 | | - seen.insert(meal, i as u16); |
| 50 | + stack.push(i); |
74 | 51 | } |
75 | 52 |
|
76 | | - best_pair |
| 53 | + result |
77 | 54 | } |
78 | 55 |
|
79 | 56 | #[cfg(test)] |
80 | 57 | mod tests { |
81 | 58 | use super::*; |
82 | 59 |
|
83 | 60 | #[test] |
84 | | - fn test_duplicate_values() { |
85 | | - let meals = [200, 200, 300]; |
86 | | - assert_eq!(find_balanced_meal_pair(&meals, 400), Some((0, 1))); |
| 61 | + fn test_sample_input_1() { |
| 62 | + let readings = vec![30, 38, 30, 36, 35, 40, 28]; |
| 63 | + let expected = vec![1, 4, 1, 2, 1, 0, 0]; |
| 64 | + assert_eq!(next_higher_readings(&readings), expected); |
87 | 65 | } |
88 | 66 |
|
89 | 67 | #[test] |
90 | | - fn test_large_difference() { |
91 | | - let meals = [1, 2, 1000]; |
92 | | - assert_eq!(find_balanced_meal_pair(&meals, 500), Some((0, 1))); |
| 68 | + fn test_sample_input_2() { |
| 69 | + let readings = vec![22, 21, 20]; |
| 70 | + let expected = vec![0, 0, 0]; |
| 71 | + assert_eq!(next_higher_readings(&readings), expected); |
93 | 72 | } |
94 | 73 |
|
95 | 74 | #[test] |
96 | | - fn test_exact_match() { |
97 | | - let meals = [150, 400, 200, 350]; |
98 | | - assert_eq!(find_balanced_meal_pair(&meals, 600), Some((1, 2))); |
| 75 | + fn test_all_increasing() { |
| 76 | + let readings = vec![1, 2, 3, 4, 5]; |
| 77 | + let expected = vec![1, 1, 1, 1, 0]; |
| 78 | + assert_eq!(next_higher_readings(&readings), expected); |
99 | 79 | } |
100 | 80 |
|
101 | 81 | #[test] |
102 | | - fn test_closest_match() { |
103 | | - let meals = [100, 200, 300, 400]; |
104 | | - assert_eq!(find_balanced_meal_pair(&meals, 550), Some((1, 2))); |
| 82 | + fn test_all_equal() { |
| 83 | + let readings = vec![10, 10, 10, 10]; |
| 84 | + let expected = vec![0, 0, 0, 0]; |
| 85 | + assert_eq!(next_higher_readings(&readings), expected); |
105 | 86 | } |
106 | 87 |
|
107 | 88 | #[test] |
108 | | - fn test_empty_array() { |
109 | | - let meals = []; |
110 | | - assert_eq!(find_balanced_meal_pair(&meals, 600), None); |
| 89 | + fn test_single_element() { |
| 90 | + let readings = vec![42]; |
| 91 | + let expected = vec![0]; |
| 92 | + assert_eq!(next_higher_readings(&readings), expected); |
111 | 93 | } |
112 | 94 |
|
113 | 95 | #[test] |
114 | | - fn test_single_element() { |
115 | | - let meals = [300]; |
116 | | - assert_eq!(find_balanced_meal_pair(&meals, 600), None); |
| 96 | + fn test_two_elements() { |
| 97 | + let readings = vec![5, 10]; |
| 98 | + let expected = vec![1, 0]; |
| 99 | + assert_eq!(next_higher_readings(&readings), expected); |
| 100 | + } |
| 101 | + |
| 102 | + #[test] |
| 103 | + fn test_large_case() { |
| 104 | + let n = 1000; // Use a smaller n for practical test execution |
| 105 | + let readings: Vec<u16> = (1..=n).rev().collect(); |
| 106 | + let expected = vec![0; n as usize]; |
| 107 | + assert_eq!(next_higher_readings(&readings), expected); |
117 | 108 | } |
118 | 109 | } |
0 commit comments