I have the following code which collects gaps in a number sequence into a separate vec
.
There will never be a case, iterating over a Vec<i32>
where .windows(2)
will yield a window with None
values. So I want to simplify things by transforming each window into a Gap
with first and last i32
values wherever the gap between 2 numbers is > 1. So a transform -> filter -> result
kind of pipeline.
My version works, but seems imperative and long-winded. I keep thinking I should be able to use .from_fn()
, .filter()
and .collect()
to generate the gap
vector without a for
loop. How would I go about this?
#[derive(Debug)]
struct Gap {
first: i32,
last: i32,
}
fn main() {
let original_numbers = vec![1, 3, 4, 7, 8, 10, 11, 13, 6, 19, 20, 21];
let mut seq = original_numbers.clone();
seq.sort();
let windows = seq.windows(2);
let mut gaps: Vec<Gap> = Vec::new();
for gap_window in windows {
let g = Gap {
first: if let Some(num) = gap_window.first() { *num + 1 } else { 0 },
last: if let Some(num) = gap_window.last() { *num - 1 } else { 0 },
};
if g.last - g.first >= 0 {
gaps.push(g)
}
}
for gap in gaps {
println!("{:?}", gap)
};
}
Output:
Gap { first: 2, last: 2 }
Gap { first: 5, last: 5 }
Gap { first: 9, last: 9 }
Gap { first: 12, last: 12 }
Gap { first: 14, last: 18 }
2 Answers 2
My version works, but seems imperative and long-winded. I keep thinking I should be able to use .from_fn(), .filter() and .collect() to generate the gap vector without a for loop. How would I go about this?
Use filter_map
. It combines filter and map, and what you are doing is essentially mapping windows into gaps and filtering out invalid gaps.
first: if let Some(num) = gap_window.first() { *num + 1 } else { 0 },
Rust has nice functions for dealing with Option
. In this case you can do:
first: gap_window.first().map(|num| *num + 1).unwrap_or(0)
But why are defaulting to zero here? Silently defaulting to garbage values when an unexpected outcome occours is the worst possible strategy. It is way way better to panic:
first: *gap_window.first().unwrap() + 1
Or even better:
first: gap_window[0] + 1
-
\$\begingroup\$ good pointers.
filter_map()
not appropriate because theGap
structs are needed later on, but good to know aboutfilter_map()
for the same project. How doesgap_window[0] + 1
unwrap theOption
? \$\endgroup\$Kim– Kim2022年10月18日 18:37:45 +00:00Commented Oct 18, 2022 at 18:37 -
\$\begingroup\$ The fact that
Gap
structs are needed later doesn't preventfilter_map
from working. See play.rust-lang.org/…. But in this case filter and then map is actually better. \$\endgroup\$Winston Ewert– Winston Ewert2022年10月18日 21:25:28 +00:00Commented Oct 18, 2022 at 21:25 -
\$\begingroup\$ @Nick,
gap_window[0]
effectively meansgap_window.get(0).unwrap()
The language designers decided that when indexing into slices it would panic on out of bounds rather than returning an option. \$\endgroup\$Winston Ewert– Winston Ewert2022年10月18日 21:28:41 +00:00Commented Oct 18, 2022 at 21:28
Thanks to Winston Ewert's answer. My final version below. Note that filter_map()
was not appropriate because the output and the filter expression are different.
#[derive(Debug)]
struct Gap {
first: i32,
last: i32,
}
fn main() {
let original_numbers = vec![1, 3, 4, 7, 8, 10, 11, 13, 6, 19, 20, 21, 22];
let mut seq = original_numbers.clone();
seq.sort();
let windows = seq.windows(2);
let mut gaps = windows.into_iter().map(|win| Gap {
first: win.first().unwrap() + 1,
last: win.last().unwrap() - 1
}).filter(|gap| gap.last - gap.first >= 0);
for gap in gaps {
println!("{:?}", gap)
};
}
Output:
Gap { first: 2, last: 2 }
Gap { first: 5, last: 5 }
Gap { first: 9, last: 9 }
Gap { first: 12, last: 12 }
Gap { first: 14, last: 18 }