根据条件在多个可能的迭代器之一上迭代

34

我正在尝试根据函数的 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);
    }
}

我遇到一个错误:

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>>`

当然,这是很有道理的,但我真的希望根据条件选择我的迭代器,因为for循环中的代码非常复杂,并且复制粘贴所有内容仅更改迭代器选择会非常丑陋和难以维护。

我尝试在两个分支上使用as Iterator<Item = i64>,但这会导致��个关于未定大小类型的错误,因为它是一个trait对象。有没有简单的方法解决这个问题?

当然,我可以使用.collect(),然后迭代该向量,因为它们返回相同的类型。 这是一个不错的快速修复方法,但对于大型列表来说似乎有点过度了。

4个回答

39
最直接的解决方案是使用一个特质对象:
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 dyn 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);
    }
}

这种解决方案的主要缺点是需要为每个具体类型分配堆栈空间。这也意味着需要为每种类型分配变量。好处是只有使用的类型需要初始化。
相同的想法,但需要堆分配的是使用Boxed trait对象
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<dyn 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);
    }
}

这在你想要从函数中返回迭代器时非常有用。它只占用一个指针的堆栈空间,只会分配所需的堆空间。
你也可以为每个可能的具体迭代器使用枚举类型

29

either crate 提供了 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);
    }
}

之前的答案一样,每个具体类型都需要栈空间。但是,您不需要为每个具体值创建单独的变量。

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

您也会在其他地方找到此类型(或语义等效物),例如 futures::Either


我喜欢下面Shep的另一个答案,因为它可以泛化到不止两个迭代器。 - Mateen Ulhaq
5
你可以创建一个Either<Either<A, B>, C>等类型的对象。 - Shepmaster
当从一个递归函数返回迭代器,并且该函数本身也是递归的(以一种具有递归性的具体类型),这种解决方案就不起作用了,但是Box<dyn Iterator>的解决方案可以。 - undefined

23

个人而言,我通常更喜欢创建一系列链接在一起的Option<Iterator>值,而不是使用Either。类似于这样:

playground

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

这种方法比使用Either不太明显,但它避免了使用另一个crate,并且有时会非常优雅。



13

这是@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())
}

这个版本中有比@Niko更多的mut,但我想这并不重要。 :) - Mateen Ulhaq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接