Using Iterators in Rust

Iterators

An iterator is responsible for iterating over each item in a collection and determining when the sequence has finished. Iterators in Rust are lazy, meaning they have no effect until you call methods that consume the iterator to use it up. Iterators can be obtained from most of the collections in the standard library.

into_iter provides an owned iterator: the collection is moved and you can no longer use the orignal collection:

fn main(){
    let v = vec![1,2,3];

    for x in v.into_iter(){
        println!("{}",x);
    }

    // you can no longer use v

}

iter provides a borrowed iterator:

fn main(){
    let mut h = HashMap::new();
    h.insert(String::from("Hello"),String::from("World"));

    for (key,value) in h.iter(){
        println!("{}:{}",key,value);
    }

}

Iterators can also be obtained from arrays as well

fn main() {
    let a =[1, 2, 3,];
    for x in a.iter() {
        println!("{}", x);
    }
    }

Consuming Iterators

We have seen the usage of iterators in the for .. in .. style of loops.Idiomatic Rust favors functional programming.Let’s see how this can be used in this way.

for_each is the functional equivalent of for .. in .. loop

fn main(){
    let v = vec!["Hello", "World", "!"].into_iter();

    v.for_each(|word| {
        println!("{}", word);
    });
}

collect can be used to transform an iterator to a collection

fn main(){
    let x = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter();
    let _: Vec<u64> = x.collect();
}

You can also obtain a HashMap using from_iter

fn main(){
    let x = vec![(1, 2), (3, 4), (5, 6)].into_iter();
    let _: HashMap<u64, u64> = HashMap::from_iter(x);
}

reduce reduces the element to a single one, by repeatedly applying a reducing operation.The reducing function is a closure with two arguments: ‘accumulator’ and an element.

fn main(){
    let values = vec![1, 2, 3, 4, 5].into_iter();
    let _sum = values.reduce(|acc, x| acc + x);


}

fold Folds every element into an accumulator by applying an operation, returning the final result.takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. The closure returns the value that the accumulator should have for the next iteration.The initial value is the value the accumulator will have on the first call.

fn main() {
let a = [1, 2, 3];

// the sum of all of the elements of the array
let sum = a.iter().fold(1, |acc, x| acc + x);

assert_eq!(sum, 7);
}

This example demonstrates the left-associative nature of fold(): it builds a string, starting with an initial value and continuing with each element from the front until the back:

fn main() {
    let numbers = [1, 2, 3, 4, 5];

    let zero = "0".to_string();

    let result = numbers.iter().fold(zero, |acc, &x| {
        format!("({acc} + {x})")
    });

    assert_eq!(result, "(((((0 + 1) + 2) + 3) + 4) + 5)");
}

Combinators

filter Creates an iterator which uses a closure to determine if an element should be yielded.

Given an element the closure must return true or false. The returned iterator will yield only the elements for which the closure returns true.

fn main(){
    let v = vec![-1, 2, -3, 4, 5].into_iter();
    let _positive_numbers: Vec<i32> = v.filter(|x: &i32| x.is_positive()).collect();
}

inspect can be used to inspect the values flowing through an iterator.

fn main(){
    let v = vec![-1, 2, -3, 4, 5].into_iter();

    let _positive_numbers: Vec<i32> = v
    .inspect(|x| println!("Before filter: {}", x))
    .filter(|x: &i32| x.is_positive())
    .inspect(|x| println!("After filter: {}", x))
    .collect(); 
}

map Takes a closure and creates an iterator which calls that closure on each element.

fn main(){
    let a = [1, 2, 3];

    let mut iter = a.iter().map(|x| 2 * x);

    assert_eq!(iter.next(), Some(2));
    assert_eq!(iter.next(), Some(4));
    assert_eq!(iter.next(), Some(6));
    assert_eq!(iter.next(), None);
}

filter_map Creates an iterator that both filters and maps. The returned iterator yields only the values for which the supplied closure returns Some(value).

fn main(){
    let v = vec!["Hello", "World", "!"].into_iter();
    let w: Vec<String> = v
    .filter_map(|x| {
        if x.len() > 2 {
        Some(String::from(x))
        } else {
        None
        }
    })
    .collect();
    assert_eq!(w, vec!["Hello".to_string(), "World".to_string()]);
}

chain merges two iterators

fn main(){
    let x = vec![1, 2, 3, 4, 5].into_iter();
    let y = vec![6, 7, 8, 9, 10].into_iter();
    let z: Vec<u64> = x.chain(y).collect();
    assert_eq!(z.len(), 10);
}

flatten can be used to flatten collections of collections

fn main(){
    let x = vec![vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 10]].into_iter();
    let z: Vec<u64> = x.flatten().collect();
    assert_eq!(z.len(), 10);
}

Now z = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ;

Composing Combinators

This is where we can combine multiple iterators and combinators to process a complex logic without using a big for loop. For example, let’s say we have a collection of strings like:

let a = vec!["1","2","-1","4","-4","100","invalid","Not a number",];

Now we want to do the following:

  1. Parse the array of stirngs into numbers
  2. filter out invalid results
  3. filter numbers less than 0
  4. collect everything in a new vector

This is how we can do it:

fn main(){
    let a = vec!["1","2","-1","4","-4","100","invalid","Not a number",];
    let _only_positive_numbers: Vec<i64> = a
        .into_iter()
        .filter_map(|x| x.parse::<i64>().ok())
        .filter(|x| x > &0)
        .collect();

}