How to avoid for loops and let statement for this program

Viewed 190

In Rust, how do I avoid writing for loops and let statement? This program is used to record which line of text the string appears on and the position it appears on the line.

I know I can use iterators and map to eliminate them, but I just contacted Rust, I don't know how to write it.

pub struct L{
    x: usize,
    y: usize,
}
pub fn foo (text: &str, string: &str)->Vec<L> {
    let mut r= Vec::new();
    let mut x=0;
    for line in text.lines(){
        for (y, _) in line.match_indices(string){
            r.push(L{
                    x : x,
                    y: y, })
        }
        x+=1;
    }
    r
}
3 Answers

This is the equivalent using iterators:

pub fn foo(text: &str, string: &str) -> Vec<L> {
    text.lines()
        .enumerate()
        .flat_map(|(x, line)| {
            line.match_indices(string).map(move |(y, _)| { L { x, y } })
        })
        .collect()
}

Broken down line for line:

.enumerate()

enumerate is used to transform an iterator of T into an iterator of (usize, T), basically zipping the original value with the index. You do this because you are keeping track of the line number with x.

.flat_map(|(x, line)| { ... })

flat_map is used to allow each value in the iterator to return its own iterator whose values are then flattened into one stream.

line.match_indices(string).map(move |(y, _)| { L { x, y } })

Here we're simply using map to take the x and the y and create an L. move is used because otherwise x will be borrowed for the closure, but the iterator and closure are returned to the flat_map closure, living longer than x. The move is just copying the x so its not an issue.

.collect()

collect is used to convert an iterator into something that can be made from the iterator by implementing FromIterator, usually collections like Vec. This is also using type-inferencing by knowing that we're returning a Vec<L>, it knows to collect into a Vec<L>.


You can verify the equivalence on the Rust Playground.

Here is the code you provided converted to use iterators:

pub fn foo(text: &str, string: &str) -> Vec<L> {
  text
    .lines()
    // Allows us to get the current count *and* the element during iteration
    .enumerate()
    // flatten the inner iterator into one single iterator
    .flat_map(|(index, line)| {
      line
        .match_indices(string)
        // the inner iterator
        .map(move |(y, _)| L { x: index, y: y })
    })
    // collect the iterator into a `Vec` of `L`
    .collect::<Vec<L>>()
}

Can try like this: Playground

#[derive(Debug)]
pub struct L {
    x: usize,
    y: usize,
}

pub fn foo (text: &str, string: &str) -> Vec<L> {
    text.lines()
        .enumerate()
        .map(|(x, line)| 
            line.match_indices(string)
                .map(move |(y, _)| L{x, y}))
        .flatten()
        .collect()
}

fn main() {
    println!("{:?}", foo(
            "aaa bbb ccc\nbbb aaa ccc\nccc aaa bbb", 
            "bbb"))
    // [L { x: 0, y: 4 }, L { x: 1, y: 0 }, L { x: 2, y: 8 }]
}
Related