【问题标题】:Lazy sequence generation in RustRust 中的延迟序列生成
【发布时间】:2013-05-01 12:34:36
【问题描述】:

如何创建其他语言所称的惰性序列或“生成器”函数?

在 Python 中,我可以使用yield,如下例所示(来自 Python 的文档),以不使用中间列表内存的方式延迟生成可迭代的序列:

# a generator that yields items instead of returning a list
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))

如何在 Rust 中做类似的事情?

【问题讨论】:

    标签: rust lazy-sequences


    【解决方案1】:

    Rust确实有生成器,但它们是高度实验性的,目前在稳定的 Rust 中不可用。

    适用于稳定的 Rust 1.0 及更高版本

    Range 处理您的具体示例。您可以将它与.. 的语法糖一起使用:

    fn main() {
        let sum: u64 = (0..1_000_000).sum();
        println!("{}", sum)
    }
    

    如果Range 不存在怎么办?我们可以创建一个对其建模的迭代器!

    struct MyRange {
        start: u64,
        end: u64,
    }
    
    impl MyRange {
        fn new(start: u64, end: u64) -> MyRange {
            MyRange {
                start: start,
                end: end,
            }
        }
    }
    
    impl Iterator for MyRange {
        type Item = u64;
    
        fn next(&mut self) -> Option<u64> {
            if self.start == self.end {
                None
            } else {
                let result = Some(self.start);
                self.start += 1;
                result
            }
        }
    }
    
    fn main() {
        let sum: u64 = MyRange::new(0, 1_000_000).sum();
        println!("{}", sum)
    }
    

    胆量是相同的,但比 Python 版本更明确。值得注意的是,Python 的生成器会为您跟踪状态。 Rust 更喜欢明确性,因此我们必须创建自己的状态并手动更新它。重要的部分是Iterator trait 的实现。我们指定迭代器产生特定类型的值 (type Item = u64),然后处理每次迭代的步进以及如何判断我们已经到达迭代的结尾。

    这个例子没有真正的Range 强大,它使用泛型,但展示了一个如何去做的例子。

    在夜间 Rust 中工作

    Nightly Rust does have generators,但它们是高度实验性的。您需要引入一些不稳定的功能来创建一个。但是,它看起来相当接近 Python 示例,并添加了一些特定于 Rust 的内容:

    // 1.43.0-nightly (2020-02-09 71c7e149e42cb0fc78a8)
    #![feature(generators, generator_trait)]
    
    use std::{
        ops::{Generator, GeneratorState},
        pin::Pin,
    };
    
    fn firstn(n: u64) -> impl Generator<Yield = u64, Return = ()> {
        move || {
            let mut num = 0;
            while num < n {
                yield num;
                num += 1;
            }
        }
    }
    

    由于当前 Rust 中的所有内容都在迭代器上运行,因此我们创建了一个适配器,将生成器转换为迭代器,以便与更广泛的生态系统一起使用。我希望这样的适配器最终会出现在标准库中:

    struct GeneratorIteratorAdapter<G>(Pin<Box<G>>);
    
    impl<G> GeneratorIteratorAdapter<G>
    where
        G: Generator<Return = ()>,
    {
        fn new(gen: G) -> Self {
            Self(Box::pin(gen))
        }
    }
    
    impl<G> Iterator for GeneratorIteratorAdapter<G>
    where
        G: Generator<Return = ()>,
    {
        type Item = G::Yield;
    
        fn next(&mut self) -> Option<Self::Item> {
            match self.0.as_mut().resume(()) {
                GeneratorState::Yielded(x) => Some(x),
                GeneratorState::Complete(_) => None,
            }
        }
    }
    

    现在我们可以使用它了:

    fn main() {
        let generator_iterator = GeneratorIteratorAdapter::new(firstn(1_000_000));
        let sum: u64 = generator_iterator.sum();
        println!("{}", sum);
    }
    

    有趣的是,它不如Iterator 的实现强大。例如,迭代器具有size_hint 方法,它允许迭代器的使用者了解剩余的元素数量。这允许在 collect 进入容器时进行优化。生成器没有任何此类信息。

    【讨论】:

    • “发电机没有任何此类信息。”这就是重点。生成器的主要用例之一是能够对无限序列进行操作,而不必将无限序列保存在内存中。这种惰性计算有很多应用。那么有什么方法可以避免将其转换回迭代器?
    • @zer0fool 转换为迭代器在这里不是问题。它仍然适用于无限序列。迭代器就像一个没有 yield 语法糖的生成器。
    • @zer0fool 标准 Rust 迭代器已经允许无限序列而不保留整个序列。有关示例,请参阅iter::repeat。生成器不会添加该功能。迭代器具有生成器没有的附加信息,这些信息允许优化。
    • @zer0fool 您可以通过直接使用生成器来避免转换为迭代器,如迭代器适配器的主体所示。您将无法在需要迭代器的地方使用它。
    【解决方案2】:

    Rust 1.34 稳定版开始,您可以使用方便的std::iter::from_fn 实用程序。它不是协程(即您仍然必须每次都返回),但至少它使您免于定义另一个结构。

    from_fn 接受一个闭包FnMut() -&gt; Option&lt;T&gt; 并反复调用它来创建一个Iterator&lt;T&gt;。在伪 Python 中,def from_fn(f): while (val := f()) is not None: yield val

    // -> Box<dyn std::iter::Iterator<Item=u64>> in Rust 2015
    fn firstn(n: u64) -> impl std::iter::Iterator<Item = u64> {
        let mut num = 0;
        std::iter::from_fn(move || {
            let result;
            if num < n {
                result = Some(num);
                num += 1
            } else {
                result = None
            }
            result
        })
    }
    
    fn main() {
      let sum_of_first_n = firstn(1000000).sum::<u64>();
      println!("sum(0 to 999999): {}", sum_of_first_n);
    }
    

    std::iter::successors 也可用。它不太通用,但可能更容易使用,因为您只是明确地传递种子值。在伪 Python 中:def successors(seed, f): while seed is not None: yield seed; seed = f(seed)

    fn firstn(n: u64) -> impl std::iter::Iterator<Item = u64> {
        std::iter::successors(
            Some(0),
            move |&num| {
                if num + 1 < n {
                    Some(num + 1)
                } else {
                    None
                }
            },
        )
    }
    

    但是,Shepmaster 的说明也适用于这些实用程序。 (tldr:经常手动滚动Iterators 内存效率更高)

    有趣的是,它不如Iterator 的实现强大。例如,迭代器具有size_hint 方法,它允许迭代器的使用者了解剩余的元素数量。这允许在 collect 进入容器时进行优化。生成器没有任何此类信息。

    (注意:返回 impl 是 Rust 2018 的一项功能。有关详细信息,请参阅 Edition Guide

    【讨论】:

    • 它有效,但并非总是如此。这不是适当的发电机/产量的最佳替代品
    • 想象一下你需要返回一些东西,返回后你需要睡 1 秒钟。 std::iter::from_fnstd::iter::successors 都无法做到这一点
    【解决方案3】:

    Rust 1.0 没有生成器功能,因此您必须使用 explicit iterators 手动完成。

    首先,将您的 Python 示例重写为具有 next() 方法的类,因为这更接近您可能在 Rust 中获得的模型。然后,您可以使用实现 Iterator 特征的结构在 Rust 中重写它。

    您也可以使用返回闭包的函数来实现类似的结果,但我认为不可能实现 Iterator 特征(因为它需要被调用才能生成一个新的结果)。

    【讨论】:

    • 根据确切的顺序,有几个可以使用的内置迭代器,例如UnfoldrCounter 加上 scan(和/或任何其他组合函数。(不幸的是,除了类型之外,还没有其他文档。)
    • 我想知道是否可以编写一个宏来定义自定义迭代器结构,但样板要少一些。
    • 这个答案非常过时了。见Shepmaster's answer
    【解决方案4】:

    你可以使用我的堆栈式 Rust generator library,它支持稳定的 Rust:

    #[macro_use]
    extern crate generator;
    use generator::{Generator, Gn};
    
    fn firstn(n: usize) -> Generator<'static, (), usize> {
        Gn::new_scoped(move |mut s| {
            let mut num = 0;
            while num < n {
                s.yield_(num);
                num += 1;
            }
            done!();
        })
    }
    
    fn main() {
        let sum_of_first_n: usize = firstn(1000000).sum();
        println!("sum ={}", sum_of_first_n);
    }
    

    或更简单地说:

    let n = 100000;
    let range = Gn::new_scoped(move |mut s| {
        let mut num = 0;
        while num < n {
            s.yield_(num);
            num += 1;
        }
        done!();
    });
    
    let sum: usize = range.sum();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-15
      • 2021-11-25
      相关资源
      最近更新 更多