【问题标题】:Is it possible to step by a different amount each iteration without creating a special iterator?是否可以在不创建特殊迭代器的情况下每次迭代步进不同的数量?
【发布时间】:2017-10-05 00:05:22
【问题描述】:

在 C 中,for 循环有一个可选的增量部分,在 Rust 中我有时会错过:

for (uint i = 0; i < max; i = step_function(i, j, k)) {
    /* many lines of code! */
}

这可以用 Rust 写成:

let mut i: u32 = 0;
while (i < max) {
    // 
    // many lines of code! 
    //
    i = step_function(i, j, k);
}

...但是如果continue 存在于“多行代码” 中的某处,这将引入错误。我个人的偏好也是将增量保持在循环的顶部。

如果不创建一个特殊的迭代器来处理这个问题,有没有一种方法可以更紧密地匹配 C 风格的循环,同时解决上面提到的两个问题?

“特殊迭代器”是指不必在 for 循环之外定义迭代器类型和方法。

虽然这看起来像是一个人为的要求,但必须为一次性使用定义一个迭代器 - 在读取和编写代码时都会增加一些开销。

虽然@kennytm 的回答展示了可重用的StepByFn 迭代器如何工作,但使用闭包会为代码添加一些原本不存在的约束。

【问题讨论】:

  • 我不确定这是否真的是一个骗局,但stackoverflow.com/questions/27893223/… 会给你答案。
  • 这个例子没有使用自定义步长,它是由任意函数步进的,使得上面的例子没有那么密切的关系。 (step_by 不能用来代替 step_function 用法)。
  • 你需要什么样的约束而闭包不能覆盖?即你有一些具体的例子吗?
  • @kennytm,过去使用闭包 - 您可以在块的主体中​​使用一些表达式,这会在闭包中产生错误。毫无疑问,有很多方法可以解决大多数情况——但这使得移植代码的过程变得不那么简单。因此询问是否可以使用 C 风格的循环。当然,使用宏(cfor 板条箱)也有优缺点,因此需要权衡解决方案 - 也许是为了获得 C 代码的初始端口,使用cfor 宏对于例如稍后的代码是有意义的之后(一旦测试到位)可以变得更加质朴

标签: loops rust control-flow


【解决方案1】:

如果你可以导入外部 crate,你应该使用itertools::iterate:

extern crate itertools;
use itertools::iterate;

fn main() {
    for i in iterate(0, |i| 2*i + 3).take_while(|i| *i < 100) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}

如果你真的缺少 C 风格的 for 循环,你可以使用 cfor crate:

#[macro_use] extern crate cfor;

fn main() {
    cfor!{ let mut i = 0; i < 100; i = 2*i + 3; {
        println!("{}", i);
        // 0 3 9 21 45 93
    }}
}

如果您限制只使用标准库,创建一个特殊的迭代器将是最惯用的方式。

fn main() {
    for i in StepByFn::new(0, 100, |i| 2*i + 3) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}

struct StepByFn<T, F> {
    begin: T,
    end: T,
    step: F,
}

impl<T, F: FnMut(&T) -> T> StepByFn<T, F> {
    pub fn new(begin: T, end: T, step: F) -> StepByFn<T, F> {
        StepByFn { begin, end, step }
    }
}

impl<T: PartialOrd, F: FnMut(&T) -> T> Iterator for StepByFn<T, F> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        if self.begin >= self.end {
            return None;
        }
        let next = (self.step)(&self.begin);
        let prev = std::mem::replace(&mut self.begin, next);
        Some(prev)
    }
}

也可以用repeat().scan()创建内联迭代器,但是很丑,不能很好的表达意图

use std::iter::repeat;

fn main() {
    for i in repeat(()).scan(0, |i, ()| { 
        let old = *i; 
        *i = 2*old + 3; 
        if old < 100 { Some(old) } else { None } 
    }) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}

【讨论】:

  • 虽然我同意您回答的内容,但我不认为这回答了具有 OP 要求的限制的问题:不创建特殊的迭代器。此外,由于 OP 未充分说明问题,jk 可能会在循环内部计算(甚至可能来自外部源)。这里介绍的所有解决方案都需要在开始循环之前知道要迭代多少的所有信息。
  • @Shepmaster ①如果iterate().take_while()repeat().scan()算作“创建一个特殊的迭代器”,那么Range::step_by也是一个特殊的迭代器。 (顺便说一句not strictly following OP's restriction is acceptable。这应该对以后的读者更有帮助。)②重新计算jk意味着使它们成为Cell。我会等待OP澄清。 ③ 我不明白你所说的“关于迭代多少的信息”是什么意思。
  • 这应该对以后的读者更有帮助——我完全同意。我只是指出 OP 中的细微差别。老实说,我不知道简单地实例化一个迭代器是否会满足限制。 关于迭代多少的信息是我试图对“所有进入闭包的东西”进行抽象。在您的示例中,这将是 2*i + 3,但可能会导致外部数据和朋友的复杂性。
  • @Shepmaster 我明白了,谢谢。如果step_function 足够简单(即它不依赖于需要在循环内重新计算的jk),那么当前的方法就足够了。太糟糕了scopeguard::defer! 在这里不起作用:(
  • @Shepmaster:指定“不使用迭代器”是 X/Y 问题。让 OP 暴露问题,答案提出解决方案。 “不使用分号”、“不使用大括号”或“不创建自定义迭代器”的限制?为什么?我们不是Perec
猜你喜欢
  • 2021-03-23
  • 1970-01-01
  • 2019-01-12
  • 2018-03-31
  • 2020-12-13
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多