【问题标题】:How can I intersperse a rust iterator with a value every n items?我怎样才能在一个 rust 迭代器中散布一个每 n 个项目的值?
【发布时间】:2022-12-16 17:50:50
【问题描述】:

我有一个字符迭代器,我想每 N 个字符添加一个换行符:

let iter = "abcdefghijklmnopqrstuvwxyz".chars();
let iter_with_newlines = todo!();
let string: String = iter_with_newlines.collect();
assert_eq("abcdefghij\nklmnopqrst\nuvwxyz", string);

所以基本上,我想在迭代器中每隔 n 个字符穿插一个换行符。我怎样才能做到这一点?

我的一些想法

如果我能做这样的事情就好了,chunks 是一种将Iterator<T> 变成Iterator<Iterator<T> 的方法:iter.chunks(10).intersperse('\n').flatten()

如果我能做这样的事情也会很酷:iter.chunks.intersperseEvery(10, '\n'),其中intersperseEvery 是一种只会散布每 n 个项目的值的方法。

【问题讨论】:

标签: rust iterator rust-itertools


【解决方案1】:

您可以使用 enumerateflat_map 进行临时分配,而无需临时分配:

use either::Either;

fn main() {
    let iter = "abcdefghijklmnopqrstuvwxyz".chars();
    let iter_with_newlines = iter
        .enumerate()
        .flat_map(|(i, c)| {
            if i % 10 == 0 {
                Either::Left(['
', c].into_iter())
            } else {
                Either::Right(std::iter::once(c))
            }
        })
        .skip(1); // The above code add a newline in first position -> skip it
    let string: String = iter_with_newlines.collect();
    assert_eq!("abcdefghij
klmnopqrst
uvwxyz", string);
}

Playground

【讨论】:

  • 哦整洁。我对为什么 flat_map 在这里工作感到困惑,因为我认为有两个级别(我在考虑 Either<some Iterator, some Iterator>),而 flat_map 应该只压平一个。但是Either impl Iterator。当从 if 返回不同的类型时,这个技巧应该有助于避免在很多情况下与 Box<dyn …> 混为一谈。
  • Either::Left / Either::Right 将内部值转换为迭代器。我第一次看到这个。好的!
【解决方案2】:

如果你不是特别关心性能,你可以使用itertools中的chunks,将块收集到Vecs,然后将你的元素作为单个元素穿插Vec,只是为了压扁整个事情最后。

use itertools::Itertools;
iter
    .chunks(3)
    .into_iter()
    .map(|chunk| chunk.collect::<Vec<_>>())
    .intersperse(vec![','])
    .flat_map(|chunk| chunk.into_iter())
    .collect::<String>();

Playground

除此之外,考虑编写自己的迭代器扩展特征,就像 itertools 一样?

【讨论】:

  • 我如何编写自己的迭代器扩展特征?
  • Stackoverflow 有很多这样的例子。 This one 似乎很容易理解(尽管您不需要 Peekable 或那里的 next 函数的内容)?
  • 这是我最终制作的:playground。您如何看待我为模块、结构和特征选择的名称?你认为其他事情会更清楚吗?
【解决方案3】:

这是我最终做的:

// src/intersperse_sparse.rs

use core::iter::Peekable;

/// An iterator adaptor to insert a particular value
/// every n elements of the adapted iterator.
///
/// Iterator element type is `I::Item`
pub struct IntersperseSparse<I>
where
    I: Iterator,
    I::Item: Clone,
{
    iter: Peekable<I>,
    step_length: usize,
    index: usize,
    separator: I::Item,
}

impl<I> IntersperseSparse<I>
where
    I: Iterator,
    I::Item: Clone,
{
    #[allow(unused)] // Although this function isn't explicitly exported, it is called in the default implementation of the IntersperseSparseAdapter, which is exported.
    fn new(iter: I, step_length: usize, separator: I::Item) -> Self {
        if step_length == 0 {
            panic!("Chunk size cannot be 0!")
        }
        Self {
            iter: iter.peekable(),
            step_length,
            separator,
            index: 0,
        }
    }
}

impl<I> Iterator for IntersperseSparse<I>
where
    I: Iterator,
    I::Item: Clone,
{
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index == self.step_length && self.iter.peek().is_some() {
            self.index = 0;
            Some(self.separator.clone())
        } else {
            self.index += 1;
            self.iter.next()
        }
    }
}

/// An iterator adaptor to insert a particular value created by a function
/// every n elements of the adapted iterator.
///
/// Iterator element type is `I::Item`
pub struct IntersperseSparseWith<I, G>
where
    I: Iterator,
    G: FnMut() -> I::Item,
{
    iter: Peekable<I>,
    step_length: usize,
    index: usize,
    separator_closure: G,
}

impl<I, G> IntersperseSparseWith<I, G>
where
    I: Iterator,
    G: FnMut() -> I::Item,
{
    #[allow(unused)] // Although this function isn't explicitly exported, it is called in the default implementation of the IntersperseSparseAdapter, which is exported.
    fn new(iter: I, step_length: usize, separator_closure: G) -> Self {
        if step_length == 0 {
            panic!("Chunk size cannot be 0!")
        }
        Self {
            iter: iter.peekable(),
            step_length,
            separator_closure,
            index: 0,
        }
    }
}

impl<I, G> Iterator for IntersperseSparseWith<I, G>
where
    I: Iterator,
    G: FnMut() -> I::Item,
{
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index == self.step_length && self.iter.peek().is_some() {
            self.index = 0;
            Some((self.separator_closure)())
        } else {
            self.index += 1;
            self.iter.next()
        }
    }
}

/// Import this trait to use the `iter.intersperse_sparse(n, item)` and `iter.intersperse_sparse(n, ||item)` on all iterators.
pub trait IntersperseSparseAdapter: Iterator {
    fn intersperse_sparse(self, chunk_size: usize, separator: Self::Item) -> IntersperseSparse<Self>
    where
        Self: Sized,
        Self::Item: Clone,
    {
        IntersperseSparse::new(self, chunk_size, separator)
    }

    fn intersperse_sparse_with<G>(
        self,
        chunk_size: usize,
        separator_closure: G,
    ) -> IntersperseSparseWith<Self, G>
    where
        Self: Sized,
        G: FnMut() -> Self::Item,
    {
        IntersperseSparseWith::new(self, chunk_size, separator_closure)
    }
}

impl<I> IntersperseSparseAdapter for I where I: Iterator {}

使用它:

// src/main.rs

mod intersperse_sparse;
use intersperse_sparse::IntersperseSparseAdapter;

fn main() {
    let string = "abcdefg";
    let new_string: String = string.chars().intersperse_sparse(3, '
').collect();
    assert_eq!(new_string, "abc
def
g");
}

【讨论】:

    【解决方案4】:

    from_fn构建一个Iterator

    let mut iter = "abcdefghijklmnopqrstuvwxyz".chars().peekable();
    let mut count = 0;
    let iter_with_newlines = std::iter::from_fn(move || match iter.peek() {
        Some(_) => {
            if count < 10 {
                count += 1;
                iter.next()
            } else {
                count = 0;
                Some('
    ')
            }
        }
        None => None,
    });
    assert_eq!(
        "abcdefghij
    klmnopqrst
    uvwxyz",
        iter_with_newlines.collect::<String>()
    );
    

    Playground

    【讨论】:

      猜你喜欢
      • 2021-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-13
      • 1970-01-01
      • 2021-11-21
      • 1970-01-01
      • 2022-12-05
      相关资源
      最近更新 更多