loading...
Cover image for Rust standard iterators

Rust standard iterators

dandyvica profile image Alain Viguier ・5 min read

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

Posted on by:

dandyvica profile

Alain Viguier

@dandyvica

Senior software engineer & sysadmin, technical architect, Linux expert. Always willing to learn new stuff.

Discussion

pic
Editor guide