Iterator Chaining
One of my favorite Rust features is its powerful iterator chaining. One basic example is using map
to apply a custom function to each element in the iterator.
fn main() { let x: Vec<usize> = vec![1, 2, 3, 4, 5]; let x_mapped: Vec<usize> = x.iter().map(|v| *v * 2).collect(); assert_eq!(x_mapped, vec![2, 4, 6, 8, 10]); }
A slightly more advanced example is trying to parse a Vec
of strings, only keeping the values we successfully parsed. In the example below, we use filter_map
to both filter and map values at the same time.
fn main() { let x: Vec<&str> = vec!["3", "hello", "8", "10", "world"]; let x_parsed: Vec<usize> = x.iter().filter_map(|v| v.parse::<usize>().ok()).collect(); println!("{:?}", x_parsed); }
filter_map
accepts an Option<T>
to filter on. In our case, parse
returns a Result<T, Err>
so we use .ok()
to convert Result<T, Err>
to Option<T>
.
We can also use scopes to create extremely versatile chaining. In the last example, we'll loop over a Vec
that contains some mock fastq reads, gather some stats, and collect into a new Vec
. Even though this is a silly example, it shows how we can use iterator chaining to provide structure to unstructured data.
#[allow(unused)] #[derive(Debug, PartialEq)] struct FastqRead<'a> { name: &'a str, seq: &'a [u8], qual: &'a [u8], length: usize, mean_error: f64, } fn collect_fastq_reads<'a>(fastq_reads: &'a [(&str, &[u8], &[u8])]) -> Vec<FastqRead<'a>> { let fastq_stats: Vec<FastqRead<'a>> = fastq_reads .iter() .map(|(name, seq, qual)| { // Calculate mean error rate. let error_sum: f64 = qual .iter() .map(|q| 10_f64.powf(-1.0 * ((q - 33) as f64) / 10.0)) .sum(); let fastq_read = FastqRead { name: name, seq: seq, qual: qual, length: seq.len(), mean_error: error_sum / qual.len() as f64, }; return fastq_read; }) .collect(); fastq_stats } fn main() { let fastq_reads: Vec<(&str, &[u8], &[u8])> = vec![ ("read_1", b"ATCG", b"????"), ("read_2", b"AAAAAAA", b"???????"), ]; let fastq_stats = collect_fastq_reads(&fastq_reads); assert_eq!( fastq_stats, vec![ FastqRead { name: "read_1", seq: b"ATCG", qual: b"????", length: 4, mean_error: 0.001 }, FastqRead { name: "read_2", seq: b"AAAAAAA", qual: b"???????", length: 7, mean_error: 0.001 } ] ); }