如何迭代多个范围或迭代器的乘积?

12

在Rust中,是否有一种自然的方法可以迭代几个范围或迭代器的“乘积”?

当您遍历多维数组或者状态空间时会遇到这种情况。例如,我想考虑一个由5个元素组成的布尔元组的所有可能值。嵌套5个for循环有点笨拙。


1
我几乎认为布尔值应该是一个特例,我想要一个不同的工具。但通用工具也可适用于其他情况。实际上,对于布尔值,许多人会在 std::bitset 上进行“迭代”(计数)。 - Mooing Duck
你想要的在Python中可以使用itertools.product实现。然而,在Rust中实现这样的功能并不像一开始看起来那么容易。通常情况下,实现它需要使用宏。 - Chris Morgan
2个回答

16

itertools crate有一个非常人性化的宏(iproduct!),用于迭代迭代器的乘积。以下是一个示例:

pub fn main() {
    let a = ['A', 'B', 'C'];
    let b = [1, 4];
    let c = [true, false];
    let d = ['x', 'y'];

    for (a, b, c, d) in itertools::iproduct!(&a, &b, &c, &d) {
        println!("{} {} {} {}", a, b, c, d);
    }
}

2
itertools 库中也有 cartesian_product 函数 - Lukas Kalbertodt

14

这里是一个可以完成任务的宏:

macro_rules! product {
    ($first:ident, $($next:ident),*) => (
        $first.iter() $(
            .flat_map(|e| std::iter::repeat(e)
                .zip($next.iter()))
        )*
    );
}

fn main() {
    let a = ['A', 'B', 'C'];
    let b = [1, 4];
    let c = [true, false];
    let d = ['x', 'y'];
    
    for (((a, b), c), d) in product![a, b, c, d] {
        println!("{} {} {} {}", a, b, c, d);
    }
}

输出:

A 1 true x
A 1 true y
A 1 false x
A 1 false y
A 4 true x
A 4 true y
etc...

Playpen示例

该宏展开为以下代码

a.iter()
    .flat_map(|e| std::iter::repeat(e).zip(b.iter()))
    .flat_map(|e| std::iter::repeat(e).zip(c.iter()))
    .flat_map(|e| std::iter::repeat(e).zip(d.iter()))
flat_map(|e| ... )将一系列迭代器组合成一个迭代器,其中e是由迭代器产生的元素。 std::iter::repeat(e)创建一个重复产生e的迭代器。 .zip( ... )同时迭代两个迭代器,并将两者的元素作为一对产生。 宏需要解释更多,最好阅读书中的宏章节

1
谢谢,但你能简要描述一下它是做什么的吗?(不是语法,只是基本技术。)我对 Rust 非常陌生,想学习而不是复制/粘贴。 :) - starwed
4
值得注意的是,这会为a中的每个元素重新创建b迭代器,并为a b中每对元素重新创建c迭代器,等等。因此,如果其中任何一个消耗了值、很昂贵或只应调用一次,那么这种策略就不能像预期那样工作。(然而,一般情况下很难生成这样的迭代器,至少不是在首先收集到第二个数据结构(如Vec)之前,我想大多数迭代器都很容易创建,因此这些问题似乎并不特别严重。) - huon
代码需要进行一些更改才能在当前版本的rustc中运行,宏定义分隔符已更改为花括号,std::iter::Repeat::new 更改为 std::iter::repeat,工作代码:https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=7ff9db3c7f53253a89992c2303f4374b - giuliano-oliveira

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