rust 是否有slice::chunks/window的等价物,让迭代器在对、三元组等上循环?

1qczuiv0  于 2022-11-30  发布在  其他
关注(0)|答案(3)|浏览(124)

一次迭代多个变量非常有用,可以重叠(slice::windows),也可以不重叠(slice::chunks)。
这仅适用于切片;为了方便起见,可以使用元组对迭代器执行此操作吗?
可以写成如下形式:

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

如果不能,它是否可以在现有迭代器上实现为一个特征?

dxpyg8gm

dxpyg8gm1#

可以使用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);
    }
}

playground)(英文)

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

playground

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

playground

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

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

55ooxyrt

55ooxyrt2#

TL;DR:在任意迭代器/集合上拥有chunkswindows的最好方法是首先将collect转换为Vec,然后在 * 该 * 上迭代。

在Rust中不可能实现所请求的精确语法。
问题在于,在Rust中,函数的签名依赖于 * 类型 *,而不是 * 值 *,尽管存在依赖类型,但很少有语言实现它(这很难)。
这就是为什么chunkswindows顺便返回子切片的原因; &[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<_>>()
由于Vecchunkswindows方法(通过实现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);
}

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

s5a0g9ez

s5a0g9ez3#

每晚

chunks版本现在每晚在上可用,名称为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();

在马厩

从Rust 1.51开始,这可以通过const泛型实现,其中迭代器为任何N生成常量大小的数组**[T; N]**。
我构建了两个独立的板条箱来实现这一点:

第一次
使用Itertools答案中给出的示例:

use iterchunks::IterChunks; // 0.1

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

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

此输出

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

大多数情况下,数组的大小是可以推断的,但是你也可以明确地指定它。另外,可以使用任何合理的大小N,没有像Itertools那样的限制。

use iterwindows::IterWindows; // 0.1

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

此输出

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

注意:windows()使用clone来多次生成元素,因此它最好用于引用,并且复制类型的成本较低。

相关问题