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

时间:2018-01-19 19:53:11

标签: rust

我有一个迭代器类型对象,每次调用它都可以返回零个,一个或多个项目。我想实现标准Iter API,即next返回Option<Self::Item>,因此可以逐项消费。

在Clojure中,我可能会使用mapcat(&#34; map and concatenate&#34;)。

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

// 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);
    }
}

鉴于我所看到的创新事物,我想知道是否有更惯用,更便宜的方式来实现这种模式。

1 个答案:

答案 0 :(得分:1)

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

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

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

let things = Outer { counter: 0 };

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

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

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

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