Conditionally return an empty iterator from flat_map

Given this definition for foo :

 let foo = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; 

I would like to be able to write code as follows:

 let result: Vec<_> = foo.iter() .enumerate() .flat_map(|(i, row)| if i % 2 == 0 { row.iter().map(|x| x * 2) } else { std::iter::empty() }) .collect(); 

but this causes an error regarding if and else clauses having incompatible types. I tried to temporarily remove map , and I tried to determine the empty vector outside the closure and return the iterator by so:

 let empty = vec![]; let result: Vec<_> = foo.iter() .enumerate() .flat_map(|(i, row)| if i % 2 == 0 { row.iter() //.map(|x| x * 2) } else { empty.iter() }) .collect(); 

It seems silly, but it compiles. If I try to uncomment map , then it still complains about if and else clauses that have incompatible types. Here is the part of the error message:

 error[E0308]: if and else have incompatible types --> src/main.rs:6:30 | 6 | .flat_map(|(i, row)| if i % 2 == 0 { | ______________________________^ 7 | | row.iter().map(|x| x * 2) 8 | | } else { 9 | | std::iter::empty() 10 | | }) | |_________^ expected struct `std::iter::Map`, found struct `std::iter::Empty` | = note: expected type `std::iter::Map<std::slice::Iter<'_, {integer}>, [ closure@src /main.rs:7:28: 7:37]>` found type `std::iter::Empty<_>` 

Playground

I know that I can write something that does what I want with some nested for loops, but I would like to know if there is a way to write it using iterators.

+5
source share
2 answers

Since Rust is statically typed, and each step in the iterator chain changes the result to a new type that captures the previous types (unless you use objects with a box stroke), you will have to write it so that both branches are covered with the same types.

One way to convey conditional void with one type is the TakeWhile iterator implementation.

 .flat_map(|(i, row)| { let iter = row.iter().map(|x| x * 2); let take = i % 2 == 0; iter.take_while(|_| take) }) 

If you don’t mind ignoring the edge case where the input iterator foo can have more than usize elements, you can also use Take instead of 0 or usize :: MAX. The advantage is to provide better size_hint() than TakeWhile .

+4
source

In your specific example, you can use filter to remove unwanted elements before calling flat_map :

 let result: Vec<_> = foo.iter() .enumerate() .filter(|&(i, _)| i % 2 == 0) .flat_map(|(_, row)| row.iter().map(|x| x * 2)) .collect(); 

If you want to use it with map instead of flat_map , you can combine calls with filter and map with filter_map , which takes a function that returns Option and only stores Some(thing) elements.

+4
source

Source: https://habr.com/ru/post/1271042/


All Articles