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)