迭代器是否有与 slice::chunks/windows 等效的东西来循环对、三元组等?

问题描述 投票:0回答:3

一次迭代多个变量、重叠 (

slice::windows
) 或不重叠 (
slice::chunks
) 非常有用。

这只适用于切片;为了方便起见,可以使用元组对迭代器执行此操作吗?

可以写成下面这样:

for (prev, next) in some_iter.windows(2) {
    ...
}

如果没有,是否可以将其作为现有迭代器的特征来实现?

iterator rust
3个回答
50
投票

可以使用

Itertools::tuples
获取迭代器的块,最多可达 4 元组:

use itertools::Itertools; // 0.9.0

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5, 6].into_iter();

    for (prev, next) in some_iter.tuples() {
        println!("{}--{}", prev, next);
    }
}

游乐场

1--2
3--4
5--6

如果您不知道迭代器是否完全适合块,您可以使用

Tuples::into_buffer
来访问任何剩余部分:

use itertools::Itertools; // 0.9.0

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5].into_iter();

    let mut t = some_iter.tuples();
    for (prev, next) in t.by_ref() {
        println!("{}--{}", prev, next);
    }
    for leftover in t.into_buffer() {
        println!("{}", leftover);
    }
}

游乐场

1--2
3--4
5

也可以使用

Itertools::tuple_windows
:

占用最多 4 元组窗口
use itertools::Itertools; // 0.9.0

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5, 6].into_iter();

    for (prev, next) in some_iter.tuple_windows() {
        println!("{}--{}", prev, next);
    }
}

游乐场

1--2
2--3
3--4
4--5
5--6

如果需要获取部分块/窗口,可以获取


32
投票

TL;DR:在任意迭代器/集合上拥有

chunks
windows
的最佳方法是首先将
collect
放入
Vec
中,然后迭代 that


所要求的确切语法在 Rust 中是不可能的。

问题在于,在 Rust 中,函数的签名取决于 types,而不是 values,虽然存在依赖类型,但实现它的语言很少(很难)。

这就是为什么

chunks
windows
顺便返回一个子切片;
&[T]
中的元素数量不是类型的一部分,因此可以在运行时决定。


让我们假设您要求的是:

for slice in some_iter.windows(2)

支持该切片的存储将位于哪里?

它无法生存:

  • 在原始集合中,因为
    LinkedList
    没有连续的存储
  • 在迭代器中,由于
    Iterator::Item
    的定义,没有可用的生命周期

因此,不幸的是,只有当后备存储是切片时才能使用切片。


如果接受动态分配,则可以使用

Vec<Iterator::Item>
作为分块迭代器的
Item

struct Chunks<I: Iterator> {
    elements: Vec<<I as Iterator>::Item>,
    underlying: I,
}

impl<I: Iterator> Chunks<I> {
    fn new(iterator: I, size: usize) -> Chunks<I> {
        assert!(size > 0);

        let mut result = Chunks {
           underlying: iterator, elements: Vec::with_capacity(size)
        };
        result.refill(size);
        result
    }

    fn refill(&mut self, size: usize) {
        assert!(self.elements.is_empty());

        for _ in 0..size {
            match self.underlying.next() {
                Some(item) => self.elements.push(item),
                None => break,
            }
        }
    }
}

impl<I: Iterator> Iterator for Chunks<I> {
    type Item = Vec<<I as Iterator>::Item>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.elements.is_empty() {
            return None;
        }

        let new_elements = Vec::with_capacity(self.elements.len());
        let result = std::mem::replace(&mut self.elements, new_elements);

        self.refill(result.len());

        Some(result)
    }
}

fn main() {
    let v = vec!(1, 2, 3, 4, 5);

    for slice in Chunks::new(v.iter(), 2) {
        println!("{:?}", slice);
    }
}

会回来:

[1, 2]
[3, 4]
[5]

精明的读者会意识到我偷偷地从

windows
切换到了
chunks

windows
更难,因为它多次返回相同的元素,这要求该元素为
Clone
。另外,由于它每次都需要返回完整的
Vec
,因此需要在内部保留一个
Vec<Vec<Iterator::Item>>

这留给读者作为练习。


最后,关于性能的说明:所有这些分配都会受到损害(特别是在

windows
情况下)。

最好的分配策略通常是分配一块内存,然后靠它生存(除非数量真的很大,在这种情况下需要流式传输)。

在 Rust 中称为

collect::<Vec<_>>()

并且由于

Vec
chunks
windows
方法(通过实现
Deref<Target=[T]>
),您可以使用它来代替:

for slice in v.iter().collect::<Vec<_>>().chunks(2) {
    println!("{:?}", slice);
}

for slice in v.iter().collect::<Vec<_>>().windows(2) {
    println!("{:?}", slice);
}

有时最好的解决方案是最简单的。


20
投票

稳定

自 Rust 1.51 起,这可以通过 const generics 实现,其中迭代器为任何 [T; N]

 生成恒定大小的数组 
N

我构建了实现这两个功能的

itermore
,在单独的扩展特征下提供
array_chunks()
array_windows()
方法。

use itermore::IterArrayChunks; // 0.7

for [a, b, c] in some_iter.by_ref().array_chunks() {
    ...
}

let rem = some_iter.into_remainder();
use itermore::IterArrayWindows; // 0.7

for [prev, next] in some_iter.array_windows() {
    ...
}

使用

Itertools
答案中给出的示例:

use itermore::IterArrayChunks; // 0.7

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5, 6].into_iter();

    for [prev, next] in some_iter.array_chunks() {
        println!("{}--{}", prev, next);
    }
}

这个输出

1--2
3--4
5--6

大多数情况下可以推断数组大小,但您也可以明确指定它。此外,可以使用任何合理的尺寸

N
,没有像
Itertools
情况那样的限制。

use itermore::IterArrayWindows; // 0.7

fn main() {
    let mut iter = vec![1, 2, 3, 4, 5, 6].into_iter().array_windows::<5>();
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
}

这个输出

Some([1, 2, 3, 4, 5])
Some([2, 3, 4, 5, 6])
None

注意:

array_windows()
使用克隆多次生成元素,因此它最好用于引用并且复制类型成本低廉。

每晚

块版本现已在每晚推出,名称为

array_chunks

#![feature(iter_array_chunks)]

for [a, b, c] in some_iter.array_chunks() {
    ...
}

它可以很好地处理余数:

#![feature(iter_array_chunks)]

for [a, b, c] in some_iter.by_ref().array_chunks() {
    ...
}

let rem = some_iter.into_remainder();

性能

如果您不迭代某些连续的集合,则迭代器版本特别有用。但是,根据您的用例,您可能会发现首先收集到

Vec
中并使用切片方法可能会更快,甚至包括将迭代器分配到
Vec
中的时间。对于需要克隆元素的
array_windows
情况尤其如此。

© www.soinside.com 2019 - 2024. All rights reserved.