有条件地从flat_map返回空迭代器

时间:2017-08-20 21:43:38

标签: iterator rust iteration

鉴于foo的定义:

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

我希望能够编写如下代码:

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();

但是这引发了关于if和else子句具有不兼容类型的错误。我尝试暂时删除map,我尝试在闭包之外定义一个空向量,然后像这样返回一个迭代器:

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();

这似乎有些愚蠢,但它编译。如果我尝试取消注释map,那么它仍会抱怨if和else子句具有不兼容的类型。以下是错误消息的一部分:

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 Link

我知道我可以用一些嵌套的for循环编写一些可以做我想要的东西,但是我想知道是否有一种简洁的方法可以使用迭代器来编写它。

2 个答案:

答案 0 :(得分:4)

由于Rust是静态类型的,并且迭代器链中的每个步骤都会将结果更改为包含先前类型的新类型(除非使用盒装特征对象),否则必须以两个分支都被覆盖的方式编写它相同的类型。

使用单一类型传达条件空性的一种方法是TakeWhile迭代器实现。

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

如果您不介意忽略输入迭代器foo可能包含usize个元素的边缘情况,您也可以使用Take代替0或usize :: MAX。它的优势在于提供了比size_hint()更好的TakeWhile

答案 1 :(得分:4)

在您的具体示例中,您可以在调用flat_map之前使用filter删除不需要的元素:

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

如果您希望将其与map代替flat_map一起使用,则可以使用filter_mapfiltermap的调用合并到一起返回Option的函数,仅保留Some(thing)的元素。