如何稳定地使用自定义步长迭代一个范围?

16

如果我想在stable Rust中使用自定义步骤进行迭代,应该怎么做?基本上像C/C++中的一些东西。

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

}

我已经尝试使用range_step_inclusive如何迭代自定义步长的范围?中的解决方案:

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`

还有什么其他的选择吗?可能是使用惯用方式来创建自定义范围。


1
我编辑了您的标题,以澄清您正在寻找适用于稳定 Rust 的解决方案,否则这将是重复问题 - Shepmaster
1
可能是重复的问题:如何使用自定义步长迭代范围? - cambunctious
6个回答

18

Rust 1.28+

Iterator::step_by is now stable:

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

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

参见:


1
我本来希望有一个更加“优雅”和内置的解决方案,但我猜这就是对于相对较新的语言而言的情况。谢谢! - Vale
1
@Sosdoc 按增量步进是一个备受期待的功能!但事实证明它很复杂。对于超出类型范围的边界情况,或者处理零步进,你该怎么办?令人惊讶的是,可能存在一些微小的细节问题。请注意,我的解决方案没有任何防止你误用它的措施。 :-) - Shepmaster
1
我知道,只是有点困惑,因为我自从摆弄 C 以来就一直在使用它,但现在却找不到了。 - Vale
1
你可以始终使用 for i in 0..(n/2) { let i = i * 2; … } - Hauleth
仍然非常有用,因为Iterator::step_by强制我们使用64位,当我们确定可以坚持使用32位时,这样做会更快。 - simonzack

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) {
    // …
}

2
我认为由于四舍五入,如果 n 为奇数,那么这会使你缺失最后一个索引。 如果 n = 5,则 (n/2) =2,所以你将得到 i 在0..2中,而迭代器不是包含的。 这只会在内部循环中提供 i = 0、i = 2,您将错过 i = 4,而 C 风格的循环则会提供它。 - coconaut
1
在这种情况下,请使用 (n + 1) / 2 - Hauleth
@hauleth:请在答案中进行编辑,否则它几乎看不见!(不要担心 rot26 的评论或这个评论会变得过时,评论本来就是注定要过时的) - Matthieu M.

3
自从这个问题被提出以来,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
我觉得我会坚持使用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);
    }
}

3

使用crate num

Cargo.toml:

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

如果您只需要创建基本的框架,请使用default-features = false

Rust:

extern crate num;

use num::range_step;

for i in range_step(0, 10, 2) {
    /*    */
}

range_step 是针对 Rust 的整数类型的通用功能。


-1
你可以使用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 不允许使用功能,而这个问题就是关于稳定版 Rust 的。 - Shepmaster
好的。我刚刚添加了一个基于过滤器的方法。 - qed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接