【问题标题】:What is a stable way to iterate on a range with custom step?使用自定义步骤迭代范围的稳定方法是什么?
【发布时间】:2015-10-01 00:22:26
【问题描述】:

如果我想在稳定的 Rust 中使用自定义步骤进行迭代,我应该怎么做?本质上类似于 C/C++

for (int i = 0; i < n; i += 2) {

}

我已经尝试过使用range_step_inclusiveHow do I iterate over a range with a custom step? 中的解决方案:

use std::iter::range_step_inclusive;
for i in range_step_inclusive(0, n, 2) {
    println!("i: {}", i);
}

但它似乎在 Rust 1.1 中不可用:

error: unresolved import `std::iter::range_step_inclusive`. There is no `range_step_inclusive` in `std::iter`

什么是替代方案?可能是创建自定义范围的惯用方式。

【问题讨论】:

标签: iterator rust


【解决方案1】:

锈 1.28+

Iterator::step_by 现已稳定:

fn main() {
    for i in (0..100).step_by(2) {
        println!("{}", i);
    }
}

锈 1.1+

你总是可以用老式的方式写出来:

fn main() {
    let mut i = 0;
    while i < 100 {
        println!("i: {}", i);
        i += 2;
    }
}

然后可以抽象出来:

use std::ops::Add;

fn step_by<T, F>(start: T, end_exclusive: T, step: T, mut body: F)
where
    T: Add<Output = T> + PartialOrd + Copy,
    F: FnMut(T),
{
    let mut i = start;
    while i < end_exclusive {
        body(i);
        i = i + step;
    }
}

fn main() {
    step_by(0, 100, 2, |i| {
        println!("i: {}", i);
    })
}

有趣的历史旁注,我相信在迭代器变得非常流行之前,最初所有的循环都是通过这样的闭包完成的。

然后你可以把它变成一个迭代器:

use std::ops::Add;

struct StepBy<T> {
    start: T,
    end_exclusive: T,
    step: T,
}

impl<T> StepBy<T> {
    fn new(start: T, end_exclusive: T, step: T) -> Self {
        Self {
            start,
            end_exclusive,
            step,
        }
    }
}

impl<T> Iterator for StepBy<T>
where
    T: Add<Output = T> + PartialOrd + Copy,
{
    type Item = T;
    fn next(&mut self) -> Option<Self::Item> {
        if self.start < self.end_exclusive {
            let v = self.start;
            self.start = self.start + self.step;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in StepBy::new(0, 100, 2) {
        println!("i: {}", i);
    }
}

另见:

【讨论】:

  • 我希望有一个更“优雅”的内置解决方案,但我想这就是新的(ish)语言会发生的事情。谢谢!
  • @Sosdoc 逐步增加是一个备受期待的补充!事实证明这很复杂。对于极端情况,例如在任一方向上超出类型的界限,您会怎么做?还是处理零步长?可能的小细节令人惊讶。请注意,我的解决方案无法阻止您滥用它。 :-)
  • 我知道,我只是有点困惑,因为我在摆弄 C 时找不到我习惯的东西。
  • 您可以随时使用for i in 0..(n/2) { let i = i * 2; … }
  • 仍然非常有用,因为Iterator::step_by 强制我们使用 64 位,当我们确定可以坚持使用 32 位时这会更快。
【解决方案2】:

自从提出这个问题以来,itertools crate 已成为相当标准的依赖项。您可以使用step() 方法非常简单地做您想做的事:

extern crate itertools; // 0.7.8
use itertools::Itertools;

fn main() {
    for i in (0..=10).step(2) {
        println!("i = {}", i);
    }
}

在你的Cargo.toml

[dependencies]
itertools = "0.7.8"

【讨论】:

    【解决方案3】:

    您可以使用iterator_step_by 功能。

    这是一个运行两个线程的示例,其中一个打印出奇数,另一个打印出偶数:

    #![feature(iterator_step_by)]
    extern crate thebook;
    
    use std::thread;
    use std::time::Duration;
    fn main() {
        let handle = thread::spawn(|| {
            for i in (1..1000).step_by(2) {
                println!("{}", i);
            }
        });
        for i in (2..1000).step_by(2) {
            println!("{}", i);
        }
        handle.join();
    }
    

    如果没有这个功能,你也可以在范围上使用过滤器:

    use std::thread;
    use std::time::Duration;
    fn main() {
        let handle = thread::spawn(|| {
            for i in (1..1000).filter(|x| x % 2 != 0) {
                println!("{}", i);
            }
        });
        for i in (2..1000).filter(|x| x % 2 == 0) {
            println!("{}", i);
        }
        handle.join();
    }
    

    【讨论】:

    • 特性在稳定的 Rust 中明确不允许,这个问题就是关于这个的。
    • 很公平。我刚刚添加了一个基于过滤器的方法。
    【解决方案4】:

    使用crate num

    Cargo.toml:

    [dependencies.num]
    version = "0.1.25"
    default-features = false
    

    由于您只需要 crate 的基础知识,请使用 default-features = false

    生锈:

    extern crate num;
    
    use num::range_step;
    
    for i in range_step(0, 10, 2) {
        /*    */
    }
    

    range_step 是 rust 整数类型的泛型。

    【讨论】:

      【解决方案5】:

      有办法使用let“重新定义”:

      for i in 0..((n + 1) / 2) {
          let i = i * 2;
          // …
      }
      

      或使用Iterator::map:

      for i in (0..((n + 1) / 2)).map(|i| i * 2) {
          // …
      }
      

      【讨论】:

      • 我认为如果 n 因四舍五入为奇数,这会使您缩短最后一个索引。如果 n = 5, (n / 2) = 2,那么 i 在 0..2 中,并且迭代器不包含在内。这只会在内部循环中提供 i = 0, i = 2,您将缺少 i = 4,因为 C 样式循环会提供。
      • 在这种情况下使用(n + 1) / 2
      • @hauleth:请在答案中编辑它,否则它几乎是不可见的! (不要担心 rot26 的评论或者这个会过时,cmets 注定会过时)
      【解决方案6】:

      我想我会坚持一个 while 循环。但是如果你真的想要一个基于迭代器的方法,你可以试试这个

      fn main(){
          let (start, step, end) = (1, 2, 20);
          for i in (0..).map(|x| start+step*x)
                        .take_while(|&x| x<end){
              println!("{:?}", i);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2015-03-09
        • 2013-03-20
        • 2012-11-05
        • 1970-01-01
        • 1970-01-01
        • 2021-10-20
        • 2021-07-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多