首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在Rust中实现多值迭代器模式?

如何在Rust中实现多值迭代器模式?
EN

Stack Overflow用户
提问于 2018-01-20 03:53:11
回答 1查看 282关注 0票数 1

我有一个迭代器类型的对象,它可以在每次调用时返回零个、一个或多个项。我想实现一个标准的Iter接口,即next返回Option<Self::Item>,这样它就可以被逐个消费了。

在Clojure中,我可能会用mapcat (“映射和连接”)来做这件事。

我目前的解决方案(感谢@Ryan)使用了flat_map,但仍然需要大量的分配:

代码语言:javascript
复制
// Desired input:
// A stateful object that implements an iterator which returns a number of results each time.
// The real code is a bit more complicated, this is the minimal example.
struct MyThing {
    counter: i32,
}

impl Iterator for MyThing {
    type Item = Vec<String>;

    fn next(&mut self) -> Option<Vec<String>> {
        self.counter += 1;
        if self.counter == 4 {
            self.counter = 1;
        }

        match self.counter {
            1 => Some(vec!["One".to_string()]),
            2 => Some(vec!["One".to_string(), "Two".to_string()]),
            3 => Some(vec![
                "One".to_string(),
                "Two".to_string(),
                "Three".to_string(),
            ]),
            _ => Some(vec![]),
        }
    }
}

fn main() {
    let things = MyThing { counter: 0 };

    // Missing piece, though the following line does the job:
    let flattened = things.flat_map(|x| x);

    // However this requires a heap allocation at each loop.

    // Desired output: I can iterate, item by item.
    for item in flattened {
        println!("{:?}", item);
    }
}

考虑到我看到的这些创新的东西,我想知道是否有一种更惯用、更便宜的方式来实现这种模式。

EN

回答 1

Stack Overflow用户

发布于 2018-01-20 22:28:21

如果您知道如何以编程方式生成“内部”值,请将Vec<String>替换为您定义的实现Iterator<Item = String>的结构。(从技术上讲,只有IntoIterator是必需的,但Iterator就足够了。)

代码语言:javascript
复制
struct Inner {
    index: usize,
    stop: usize,
}

impl Inner {
    fn new(n: usize) -> Self {
        Inner { index: 0, stop: n }
    }
}

impl Iterator for Inner {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        static WORDS: [&str; 3] = ["One", "Two", "Three"];
        let result = if self.index < self.stop {
            WORDS.get(self.index).map(|r| r.to_string())
        } else {
            None
        };
        self.index += 1;
        result
    }
}

因为Inner实现了Iterator<Item = String>,所以可以像Vec<String>一样遍历它。但是Inner不必预先分配Vec并一个接一个地使用项目;它可以按需懒惰地创建每个String

“外部”迭代器只是一个实现Iterator<Item = Inner>的结构,同样懒惰地构造每个Inner

代码语言:javascript
复制
struct Outer {
    counter: i32,
}

impl Iterator for Outer {
    type Item = Inner;

    fn next(&mut self) -> Option<Inner> {
        self.counter = 1 + self.counter % 3;

        Some(Inner::new(self.counter as usize))
    }
}

正如您所知道的,Iterator::flat_map展平了嵌套结构,因此类似于下面的works

代码语言:javascript
复制
let things = Outer { counter: 0 };

for item in things.flat_map(|x| x).take(100) {
    println!("{:?}", item);
}

在实际的代码中,InnerOuter在大多数情况下可能与这个示例有很大的不同。例如,在不执行与分配Vec相同的操作的情况下编写Inner不一定是可能的。因此,这些迭代器的精确形状和语义取决于有关用例的具体信息。

以上假设Inner在某种程度上是有用的,或者更容易独立实现。您可以很容易地编写一个结构来迭代序列,而不需要进行扁平化,但是您还必须将内部迭代器状态( index字段)放入Outer

代码语言:javascript
复制
struct Outer {
    index: usize,
    counter: i32,
}

impl Iterator for Outer {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        static WORDS: [&str; 3] = ["One", "Two", "Three"];
        let result = WORDS.get(self.index).map(|r| r.to_string());
        self.index += 1;
        if self.index >= self.counter as usize {
            self.counter = 1 + self.counter % 3;
            self.index = 0;
        };
        result
    }
}

fn main() {
    let things = Outer { counter: 1, index: 0 };

    for item in things.take(100) {
        println!("{:?}", item);
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48348575

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档