I am writing a function to convert string dates in a vector to pixel locations on a canvas. I initially achieved this via a series of for loops. But I would really like to be able to do this via an iterator with a series of filter_maps and maps as required. I think I have most of it figured out but in the first map (duration) I cannot figure out how to divide every element in the iterator, including the last element, by the last element. Any assistance will be appreciated.
pub fn line_graph_x_data() -> Result<Vec<f64>, CustomError> {
let date_vec = [
"2026-02-02".to_string(),
"2026-03-14".to_string(),
"2026-03-29".to_string(),
"2026-04-09".to_string(),
"2026-05-22".to_string(),
"2026-07-15".to_string(),
];
// 1) convert strings to naive dates
// 2) Determine number of days between each date and first date ("2026-02-02") in the date_vec
// 3) Determine number of days between top and bottom dates (earliest to most recent)
// 4) Apply formula (((each_value - min_vallue)/(max_value - min_value)) * 600) + 100) and round
// working code below #################
// let mut navidate_vec: Vec<NaiveDate> = Vec::new();
// for i in 0..date_vec.len() {
// let nav_date = NaiveDate::parse_from_str(&date_vec[i], "%Y-%m-%d")?;
// navidate_vec.push(nav_date);
// }
// let mut numerator_vec = Vec::new();
// for i in 0..navidate_vec.len() {
// let day_delta = navidate_vec[i].signed_duration_since(navidate_vec[0]).num_days();
// numerator_vec.push(day_delta);
// }
// let denominator = numerator_vec[numerator_vec.len()-1];
// let mut x_axis_pixels = Vec::new();
// for i in 0..numerator_vec.len() {
// let num_pixels = (((numerator_vec[i] as f64 / denominator as f64)) * 600.0) + 100.0;
// x_axis_pixels.push(num_pixels.round());
// }
// Ok(x_axis_pixels)
// working code above #################
let x_axis = date_vec.iter()
.filter_map(|item| {
let nav_date = NaiveDate::parse_from_str(&item, "%Y-%m-%d").ok();
return nav_date
})
.filter_map(|nav_date| {
let starting_date = NaiveDate::parse_from_str(&date_vec[0], "%Y-%m-%d").ok().expect("error");
let num_days = nav_date.signed_duration_since(starting_date)
.num_days();
return Some(num_days)
})
.map(|duration| {
let ratio = duration as f64 / last duration item as f64 ???????
return ratio
})
.map(|ratio| {
let x_values = ((ratio * 600.0) + 100.0).round();
return x_values
})
.collect::<Vec<f64>>();
}
Iterators are great for replacing for loops, but they don't mimic indexing. Fortunately you want the last item, and you're working with a DoubleEndedIterator, so you can get the last element. This won't work if it's not a DoubleEndedIterator or if your map functions have state that is sensitive to order.
I also got rid of a bunch of clippy warnings.
// moved this out so it doesn't have to be computed every time
let starting_date = NaiveDate::parse_from_str(&date_vec[0], "%Y-%m-%d").expect("error");
let mut duration_iter = date_vec
.iter()
.filter_map(|item| NaiveDate::parse_from_str(item, "%Y-%m-%d").ok())
.map(|nav_date| nav_date.signed_duration_since(starting_date).num_days());
// this is a DoubleEndedIterator so you can get the last item directly
let last_duration = duration_iter.next_back().unwrap();
let x_axis = duration_iter
.chain([last_duration]) // add the last one back in
.map(|duration| duration as f64 / last_duration as f64)
// .chain([1.0]) or you could just put a 1 here since anything / anything = 1
.map(|ratio| ((ratio * 600.0) + 100.0).round())
.collect::<Vec<f64>>();
Thank you. Did not know you could crate a collection with an iterator and not have a ".collect" every time. Two questions. 1) In .chain([last_duration]), the square brackets are to convert a single value into an array which implements the iterator trait while i64 does not? 2) I did not understand the commented out line starting with .chain([1.0]).
The IntoIterator trait, but otherwise yes. iter::once(value) is an alternative.
They meant: instead of adding the last value back to the end and dividing by the last value later, you could just add 1.0 to the end after the division happens for everything before the last element.
let x_axis = duration_iter
- .chain([last_duration]) // add the last one back in
.map(|duration| duration as f64 / last_duration as f64)
+ .chain([1.0]) // `add last_duration / last_duration` (`== 1.0`) to the end
.map(|ratio| ((ratio * 600.0) + 100.0).round())
.collect::<Vec<f64>>();
Or you could add 700.0 after the map, using the same sort of reasoning.
let x_axis = duration_iter
.map(|duration| duration as f64 / last_duration as f64)
- .chain([1.0]) // `add last_duration / last_duration` (`== 1.0`) to the end
.map(|ratio| ((ratio * 600.0) + 100.0).round())
+ .chain([700.0])
.collect::<Vec<f64>>();