Today, I'll tackle Rust standard iterators, which can be browsed here: Rust Iterators
In you ever read this page, you've probably stumbled upon a list of methods along with some examples, but even after reading this full page, you may have wondered: how to use all these methods ?
Because the examples are based, for the most part, on a vector of integers (which by the way implement the Copy trait), it's pretty easy to use with such an iterable. But once you have a more complicated iterator like a vector of structs, this might be more tedious to use.
That's why I wrote this article.
As the base for my examples, I've use the Mendeleiev periodic list of elements available as a CSV file here: https://gist.github.com/GoodmanSciences/c2dd862cd38f21b0ad36b8f96b4bf1ee (beware to fix lines which contain spaces betweens fields, as Rust might crash when loading the CSV).
I've create a simple function to load the data into a struct, whose members map (snake case though) the CSV column names:
pub fn load_as_vector() -> Result<Vec<Element>, Box<dyn Error>> { // load CSV let reader = std::fs::File::open("elements.csv").unwrap(); let mut rdr = csv::Reader::from_reader(reader); // create a vector of structs let mut v: Vec<Element> = Vec::new(); for result in rdr.deserialize() { let record: Element = result?; v.push(record); } Ok(v) } The vector is loaded using this dedicated method (you can find the whole code here: https://github.com/dandyvica/articles/tree/master/combinators)
let v = element::load_as_vector()?; I didn't respect the alphabetic order of all the methods, I rather adopted an incremental approach, with the simplest ones in the beginning of the article. I tried to express what the method is representing for the vector data, instead of writing its definition which you can get anyway. I also unwrap() the results when possible, because a lot of iterators methods usually return an Option type.
I also didn't cover all the methods, by lack of time. If you get ideas on how to add other examples for this missing methods, feel free to reach out ! In addition, the following examples are probably not optimized and some better combination of iterators should exist.
Beware I'm by no means a chemical engineer, I used this material just to illustrate my article.
Iterator methods
- count(): there're 118 elements in the Mendeleiev table
let n = v.iter().count(); assert_eq!(n, 118); - last(): Oganesson is the last element
let last_element = v.iter().last().unwrap(); assert_eq!(last_element.element, "Oganesson"); - nth(): Uranium is the 92th element (vector is indexing from 0), but there's not a 119th element
let uranium = v.iter().nth(91).unwrap(); assert_eq!(uranium.element, "Uranium"); assert!(v.iter().nth(118).is_none()); - map():: Carbon is the 6th element
let mut mapped = v .iter() .map(|x| (x.atomic_number, x.element.as_ref(), x.symbol.as_ref())); assert_eq!(mapped.nth(5).unwrap(), (6u8, "Carbon", "C")); - collect(): create a vector of elements' names
let names: Vec<_> = v.iter().map(|x| &x.element).collect(); assert_eq!(names[0..2], ["Hydrogen", "Helium"]); - take(): the 2 first elements are Hydrogen and Helium
let first_2: Vec<_> = v.iter().take(2).map(|x| x.element.clone()).collect(); assert_eq!(first_2, ["Hydrogen", "Helium"]); - take_while(): there're 10 elements with less than 10 neutrons
let less_than_10e = v.iter().take_while(|x| x.number_of_neutrons <= 10); assert_eq!(less_than_10e.count(), 10); - any(): there's at least one element with more than 50 electrons
assert!(v.iter().any(|x| x.number_of_neutrons > 50)); - all(): all elements have their symbol composed by 1 or 2 letters (e.g.: C or Na)
assert!(v.iter().all(|x| x.symbol.len() == 1 || x.symbol.len() == 2)); - cycle(): when cycling through elements, Lithium is the 120th element
assert_eq!(v.iter().cycle().nth(120).unwrap().element, "Lithium"); - find(): Helium is the first element whose name ends with ium
let helium = v.iter().find(|x| x.element.ends_with("ium")).unwrap(); assert_eq!(helium.element, "Helium"); - filter() and for_each(): there're 11 gases
let gases: Vec<_> = v.iter().filter(|x| x.phase == "gas").collect(); assert_eq!(gases.iter().count(), 11); v.iter() .filter(|x| x.phase == "gas") .for_each(|x| println!("{:?}", x.element)); // gives: // "Hydrogen" // "Helium" // "Nitrogen" // "Oxygen" // "Fluorine" // "Neon" // "Chlorine" // "Argon" // "Krypton" // "Xenon" // "Radon" - filter_map(): there're 37 radioactive elements
let radioactives: Vec<_> = v .iter() .filter_map(|x| x.radioactive.as_ref()) .filter(|x| **x == YN::yes) .collect(); assert_eq!(radioactives.iter().count(), 37); - enumerate(): the last element index is 117
let (i, _) = v.iter().enumerate().last().unwrap(); assert_eq!(i, 117); - skip_while(): the first non-gas is Lithium
let first_non_gas = v.iter().skip_while(|x| x.phase == "gas" ).next().unwrap(); assert_eq!(first_non_gas.element, "Lithium"); - zip(): Uranium mass number is 238
let neutrons = v.iter().map(|x| x.number_of_neutrons); let protons = v.iter().map(|x| x.number_of_protons); let mass_numbers: Vec<_> = neutrons.zip(protons).map(|(x, y)| x + y).collect(); assert_eq!(mass_numbers[91], 238); - chain(): when listing gases and solids, Lithium is the first element, Radon the last
let all_gases = v.iter().filter(|x| x.phase == "gas"); let all_solids = v.iter().filter(|x| x.phase == "solid"); let gases_and_solids: Vec<_> = all_solids.chain(all_gases).collect(); assert_eq!(gases_and_solids.iter().nth(0).unwrap().element, "Lithium"); assert_eq!(gases_and_solids.iter().last().unwrap().element, "Radon"); - position(): searches for the Potassium element
let potassium = v.iter().position(|x| x.element == "Potassium").unwrap(); assert_eq!(v[potassium].symbol, "K"); - rposition(): Radon is the last gas
let last_gas = v.iter().rposition(|x| x.phase == "gas").unwrap(); assert_eq!(v[last_gas].element, "Radon"); - max_by(): the heaviest non-artificial element is Uranium
use std::cmp::Ordering; let cmp = |x: &Element, y: &Element| -> Ordering { if x.atomic_mass < y.atomic_mass { Ordering::Less } else if x.atomic_mass > y.atomic_mass { Ordering::Greater } else { Ordering::Equal } }; let heaviest = v .iter() .filter(|x| x.phase != "artificial") .max_by(|x, y| cmp(x, y)) .unwrap(); assert_eq!(heaviest.symbol, "U"); - rev(): the last element when reversing the vector is Hydrogen
let hydrogen = v.iter().rev().last().unwrap(); assert_eq!(hydrogen.symbol, "H"); - max_by_key(): the longuest element's name is Rutherfordium
let longuest = v.iter().max_by_key(|x| x.element.len()).unwrap(); assert_eq!(longuest.element, "Rutherfordium"); - max(): Carbon was the first element discovered, Tennessine the last
//use std::cmp::Ordering; impl Ord for Element { fn cmp(&self, other: &Self) -> Ordering { if self.year < other.year { Ordering::Less } else if self.year > other.year { Ordering::Greater } else { Ordering::Equal } } } impl PartialOrd for Element { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } } impl PartialEq for Element { fn eq(&self, other: &Self) -> bool { self.year == other.year } } impl Eq for Element {} let first_discovered = v.iter().min().unwrap(); assert_eq!(first_discovered.element, "Carbon"); let last_discovered = v.iter().max().unwrap(); assert_eq!(last_discovered.element, "Tennessine"); Hope this helps ! Feel free to comment.
Photo by Bill Oxford on Unsplash
Top comments (0)