有条件地迭代几个可能的迭代器之一

Lin*_*ope 9 rust

我正在尝试根据Option函数的输入切换行为.想法是基于给定Option是否存在来迭代.这是一个最小的,如果愚蠢的例子:

use std::iter;

fn main() {
    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    for i in match x {
        None => 1..5,
        Some(x) => iter::repeat(x).take(5),
    } {
        println!("{}", i);
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到一个错误:

error[E0308]: match arms have incompatible types
  --> src/main.rs:7:14
   |
7  |       for i in match x {
   |  ______________^
8  | |         None => 1..5,
9  | |         Some(x) => iter::repeat(x).take(5),
   | |                    ----------------------- match arm with an incompatible type
10 | |     } {
   | |_____^ expected struct `std::ops::Range`, found struct `std::iter::Take`
   |
   = note: expected type `std::ops::Range<{integer}>`
              found type `std::iter::Take<std::iter::Repeat<i64>>`
Run Code Online (Sandbox Code Playgroud)

当然,这非常有意义,但我真的想根据条件选择我的迭代器,因为for循环中的代码是非平凡的,并且复制粘贴所有这些只是为了改变迭代器选择会很漂亮丑陋而不可维护.

我尝试as Iterator<Item = i64>在两个武器上使用,但这给了我一个关于unsized类型的错误,因为它是一个特征对象.有一个简单的方法来解决这个问题吗?

当然,我可以使用.collect()它,因为它们返回相同的类型并迭代该向量.这是一个很好的快速修复,但对于大型列表似乎有点过分.

She*_*ter 14

你需要引用一个特征:

use std::iter;

fn main() {
    let mut a;
    let mut b;

    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    let iter: &mut Iterator<Item = i64> = match x {
        None => {
            a = 1..5;
            &mut a
        }
        Some(x) => {
            b = iter::repeat(x).take(5);
            &mut b
        }
    };

    for i in iter {
        println!("{}", i);
    }
}
Run Code Online (Sandbox Code Playgroud)

此解决方案的主要缺点是您必须为每种具体类型分配堆栈空间.这也意味着每种类型的变量.一件好事是只需要初始化已使用的类型.

同样的想法,但需要堆分配是使用盒装特征对象:

use std::iter;

fn main() {
    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    let iter: Box<Iterator<Item = i64>> = match x {
        None => Box::new(1..5),
        Some(x) => Box::new(iter::repeat(x).take(5)),
    };

    for i in iter {
        println!("{}", i);
    }
}
Run Code Online (Sandbox Code Playgroud)

当您想要从函数返回迭代器时,这非常有用.采用的堆栈空间是单个指针,只分配所需的堆空间.


She*_*ter 10

任一箱提供Either类型.如果两个Either都是迭代器,那么Either:

extern crate either;

use either::Either;
use std::iter;

fn main() {
    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    let iter = match x {
        None => Either::Left(1..5),
        Some(x) => Either::Right(iter::repeat(x).take(5)),
    };

    for i in iter {
        println!("{}", i);
    }
}
Run Code Online (Sandbox Code Playgroud)

之前的答案一样,这仍然需要您拥有的每种具体类型的堆栈空间.但是,每个具体值不需要单独的变量.

与特征对象引用不同,此类型也可以从函数返回.与盒装特征对象相比,无论选择哪种具体类型,它都将始终在堆栈上使用固定大小.

你会在其他地方找到这种类型(或语义等价物),例如 futures::Either

  • @MateenUlhaq 你可以做一个 `Either&lt;Either&lt;A, B&gt;, C&gt;` 等等。 (3认同)

Nik*_*kis 9

就个人而言,Either我经常更愿意创建一系列Option<Iterator>链接在一起的价值观.像这样的东西:

操场

use std::iter;

fn main() {
    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    for i in pick(x) {
        println!("{}", i);
    }
}

fn pick(opt_x: Option<i64>) -> impl Iterator<Item = i64> {
    let iter_a = if let None = opt_x {
        Some(1..5)  
    } else {
        None
    };

    let iter_b = if let Some(x) = opt_x {
        Some(iter::repeat(x).take(5))
    } else {
        None
    };

    iter_a.into_iter().flatten().chain(iter_b.into_iter().flatten())
}
Run Code Online (Sandbox Code Playgroud)

它比使用时更不明显Either,但它避免了另一个板条箱,有时它非常优雅.


luc*_*trv 6

这是@Niko出色的解决方案的一种变体,它使用单个match表达式而不是多个if let表达式,这在处理更多条件情况时可能更方便:

use std::iter;

fn main() {
    let x: Option<i64> = None;

    // Repeat x 5 times if present, otherwise count from 1 to 5
    for i in pick(x) {
        println!("{}", i);
    }
}

fn pick(opt_x: Option<i64>) -> impl Iterator<Item = i64> {
    let mut iter_a = None;
    let mut iter_b = None;

    match opt_x {
        None => iter_a = Some(1..5),
        Some(x) => iter_b = Some(iter::repeat(x).take(5)),
    }

    iter_a.into_iter().flatten().chain(iter_b.into_iter().flatten())
}
Run Code Online (Sandbox Code Playgroud)