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

211

我在 Rust 中如何迭代一个范围,步长不是 1?我来自 C++ 背景,希望实现类似下面的功能:

for(auto i = 0; i <= n; i+=2) {
    //...
}
在 Rust 中,我需要使用 range 函数,但似乎没有第三个参数可用于设置自定义步长。我该如何实现这一点?
5个回答

312

range_step_inclusiverange_step 已经不再使用。

从 Rust 1.28 开始,Iterator::step_by 是稳定的:

fn main() {
    for x in (1..10).step_by(2) {
        println!("{}", x);
    }
}

1
请参考以下链接:https://doc.rust-lang.org/core/ops/struct.Range.html#method.step_by 和 https://github.com/rust-lang/rust/issues/27741。 - maxschlepzig
1
请注意,此方法不支持在32位大小类型的机器上进行64位步骤。 - user202729
如果步长是值的一半呢? - Leandro
4
不幸的是,这不能很好地处理负步长。如果你想做,比如说,(11..=1).step_by(-2)(从11到1(包括)的奇数递减),你必须执行(1..12).step_by(2).rev(),这不太直观。 - BallpointBen

16

.step_by方法稳定之前,我认为可以使用Iterator轻松实现您想要的功能(这也是Range的本质):

struct SimpleStepRange(isize, isize, isize);  // start, end, and step

impl Iterator for SimpleStepRange {
    type Item = isize;

    #[inline]
    fn next(&mut self) -> Option<isize> {
        if self.0 < self.1 {
            let v = self.0;
            self.0 = v + self.2;
            Some(v)
        } else {
            None
        }
    }
}

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

如果需要迭代不同类型的多个范围,可以按照以下方式使代码通用:
use std::ops::Add;

struct StepRange<T>(T, T, T)
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone;

impl<T> Iterator for StepRange<T>
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone
{
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<T> {
        if self.0 < self.1 {
            let v = self.0.clone();
            self.0 = &v + &self.2;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in StepRange(0u64, 10u64, 2u64) {
        println!("{}", i);
    }
}

我会让你去除上限检查以创建一个开放式结构,如果需要无限循环......
这种方法的优点是它可以与“for”语法一起使用,并且即使不稳定的功能可用,它也将继续工作;此外,与使用标准“Range”的去糖化方法不同,它不会通过多个“.next()”调用失去效率。缺点是需要几行代码来设置迭代器,因此只有在具有许多循环的代码中才值得使用。

1
通过向您的第二个选项添加另一种类型 U,您可以使用支持与不同类型相加并仍产生 T 的类型。例如时间和持续时间。 - Ryan
1
@Ryan,这似乎是个好主意,应该可以行得通,使用以下定义的结构体: struct StepRange<T>(T, T, U) where for<'a, 'b> &'a T: Add<&'b U, Output = T>, T: PartialOrd, T: Clone; 这将允许输入的T和U类型有不同的生命周期。 - GordonBGood

6
您需要编写C++代码:
您需要编写C++代码:
for (auto i = 0; i <= n; i += 2) {
    //...
}

您可以像下面这样在Rust中编写:

let mut i = 0;
while i <= n {
    // ...
    i += 2;
}

我认为 Rust 版本更易于阅读。

关于在循环中插入“continue”,我认为即使在for结构中,也只有在条件分支内才会这样做。如果是这样的话,在while结构中在“continue”之前增加条件分支,然后它应该按预期工作。或者我有什么遗漏吗? - WDS
1
@WDS 这是违反直觉的繁琐工作,只为了让语言的一个基本功能“continue”正常工作。虽然这样做是可行的,但这种设计会鼓励产生错误。 - Chai T. Rex

5

5

如果您要按照预定义的步长(如2)进行迭代,可以使用迭代器手动迭代。例如:

let mut iter = 1..10;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    iter.next();
}

你甚至可以使用这个方法按任意数量进行步进(尽管这肯定会变得更长,更难消化):
let mut iter = 1..10;
let step = 4;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    for _ in 0..step-1 {
        iter.next();
    }
}

1
为什么要使用这个而不是 forstep_by - Sergio Tulentsev

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