This question is with respect to Day 1, Part 2 of the 2021 Advent Of Code challenge, which requires comparing values from a rolling window taken from integers streamed as input. This answer is correct, insofar as it emits the intended output, but I'm unclear as to the quality of the code.
(This challenge is no longer scored for points, making it within the rules to post/submit/stream/discuss/otherwise share answers).
My overall goal here is to learn to write reasonably good Rust code; all advice towards that end will be gratefully accepted.
use std::collections::VecDeque;
use std::error;
use std::error::Error;
use std::io;
use std::io::BufRead;
use std::ops::{Add, AddAssign, Sub};
use std::result::Result;
pub struct WindowedSumIter<'a, T, E>
where
T: Default + Add<Output = T> + Sub<Output = T> + AddAssign + Copy,
{
source: &'a mut dyn Iterator<Item = Result<T, E>>,
window: VecDeque<T>,
sum: T,
finished: bool,
}
impl<'a, T, E> WindowedSumIter<'a, T, E>
where
T: Default + Add<Output = T> + Sub<Output = T> + AddAssign + Copy,
{
pub fn new(
source: &'a mut dyn Iterator<Item = Result<T, E>>,
window_size: u32,
) -> Result<Self, E> {
let mut sum: T = T::default();
let mut window: VecDeque<T> = VecDeque::new();
let mut finished: bool = false;
// fill the window at initialization time
for _ in 0..window_size {
let item = source.next();
match item {
None => {
finished = true;
}
Some(maybe_value) => match maybe_value {
Ok(value) => {
sum = sum + value;
window.push_back(value);
}
Err(e) => {
return Result::Err(e);
}
},
}
}
Ok(WindowedSumIter {
source,
window,
sum,
finished,
})
}
}
impl<'a, T, E> Iterator for WindowedSumIter<'a, T, E>
where
T: Default + Add<Output = T> + Sub<Output = T> + AddAssign + Copy,
{
type Item = Result<T, E>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
None
} else {
let old_value = self.window.pop_front().unwrap();
let maybe_new_value = self.source.next();
match maybe_new_value {
None => {
self.finished = true;
Some(Result::Ok(self.sum))
}
Some(new_value_or_error) => match new_value_or_error {
Ok(new_value) => {
let orig_sum = self.sum;
self.sum += new_value - old_value;
self.window.push_back(new_value);
Some(Result::Ok(orig_sum))
}
Err(e) => Some(Result::Err(e)),
},
}
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let stdin = io::stdin();
let mut depth_increase_count: i32 = 0;
{
let mut integers = stdin.lock().lines().map(|result| {
result
.map_err(|e| Box::<dyn error::Error>::from(e))
.and_then(|s| {
s.parse::<i32>()
.map_err(|e| Box::<dyn error::Error>::from(e))
})
});
let mut rolling_totals = WindowedSumIter::new(&mut integers, 3)?;
let mut last_depth = rolling_totals.next().unwrap().unwrap();
for maybe_integer in rolling_totals {
let current_depth = maybe_integer.unwrap();
if current_depth > last_depth {
depth_increase_count += 1;
}
last_depth = current_depth;
}
}
println!("{}", depth_increase_count);
return Result::Ok(());
}
While all this code should be considered in-scope, I'm particularly curious as to whether there's a better way to generate an iterator over integers parsed from stdin (the statement starting with let mut integers =
) while keeping the error handling fairly flat (avoiding the need to have std::io::Error
s and std::num::ParseIntError
s handled separately by consumers of that iterator, with distinct levels of unwrapping).
For the record: There is another Advent of Code Day 1 solution at Advent of Code 2021: day 1 however, the linked question does not try to create an iterator to generate rolling windows, so I believe the new question has enough novelty to justify its creation.