You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中无需collect,如何用惯用组合子方式懒拆分迭代器?

如何用Rust惯用的组合子方式惰性拆分迭代器?

我想找到符合Rust惯用写法、适配组合子的方式,将一个迭代器拆分为多个迭代器。切片提供了split函数,但如果把迭代器collect成Vec<_>再用这个方法,会遍历所有元素完成收集,失去惰性特性。

有没有无需collect、也不用手动写for循环的实现方式?比如在《Advent of Code 2022》第1天的题目里,我需要按空行拆分字符串行迭代器,当前的实现需要先collect,代码如下:

pub fn part_one(input: &str) -> Result<String> {
    let result = input.lines()
        // 我不想在这里用collect
        .collect::<Vec<_>>()
        .split(|line| line.is_empty())
        .map(|inventory| inventory.into_iter()
            .filter_map(|item| item.parse::<i32>().ok())
            .sum::<i32>()
        )
        .max().ok_or("I guess input was empty")?;

    Ok(result.to_string())
}

希望能去掉这个collect调用。


方案1:使用itertools库的组合子(推荐)

Rust标准库目前没有内置惰性拆分迭代器的组合子,但社区维护的itertools库提供了split_when方法,可以实现惰性的按条件拆分迭代器,完全符合你的需求。

首先在Cargo.toml中添加依赖:

[dependencies]
itertools = "0.12.0"

然后修改你的代码,替换掉collect和切片split,改用split_when:

use itertools::Itertools;
use std::error::Error;

pub fn part_one(input: &str) -> Result<String, Box<dyn Error>> {
    let result = input.lines()
        // 按空行惰性拆分,每个拆分出的子迭代器包含连续的非空行
        .split_when(|line| line.is_empty())
        .map(|inventory_iter| {
            inventory_iter
                .filter_map(|item| item.parse::<i32>().ok())
                .sum::<i32>()
        })
        .max()
        .ok_or("Input was empty")?;

    Ok(result.to_string())
}

split_when会惰性地遍历原迭代器,每次遇到满足条件的元素(这里是空行)时,就结束当前的子迭代器,开始下一个。整个过程不会提前收集所有元素,完美保留了迭代器的惰性特性。

方案2:手动实现惰性拆分迭代器(无需第三方库)

如果不想引入外部依赖,也可以手动实现一个符合Iterator trait的拆分器,核心逻辑是缓存原迭代器的状态,每次生成子迭代器时,持续读取原迭代器直到遇到拆分条件。

示例实现大致如下:

use std::iter::Peekable;

struct SplitIter<I, F>
where
    I: Iterator,
    F: Fn(&I::Item) -> bool,
{
    iter: Peekable<I>,
    split_predicate: F,
}

impl<I, F> SplitIter<I, F>
where
    I: Iterator,
    F: Fn(&I::Item) -> bool,
{
    fn new(iter: I, split_predicate: F) -> Self {
        SplitIter {
            iter: iter.peekable(),
            split_predicate,
        }
    }
}

impl<I, F> Iterator for SplitIter<I, F>
where
    I: Iterator,
    F: Fn(&I::Item) -> bool,
{
    type Item = SplitSubIter<I, F>;

    fn next(&mut self) -> Option<Self::Item> {
        // 跳过开头连续的拆分元素(比如开头的空行)
        while let Some(item) = self.iter.peek() {
            if !(self.split_predicate)(item) {
                break;
            }
            self.iter.next();
        }

        if self.iter.peek().is_none() {
            return None;
        }

        Some(SplitSubIter {
            parent: self,
        })
    }
}

struct SplitSubIter<'a, I, F>
where
    I: Iterator,
    F: Fn(&I::Item) -> bool,
{
    parent: &'a mut SplitIter<I, F>,
}

impl<'a, I, F> Iterator for SplitSubIter<'a, I, F>
where
    I: Iterator,
    F: Fn(&I::Item) -> bool,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<Self::Item> {
        self.parent.iter.peek().and_then(|item| {
            if (self.parent.split_predicate)(item) {
                self.parent.iter.next(); // 消耗拆分元素
                None
            } else {
                self.parent.iter.next()
            }
        })
    }
}

// 为Iterator添加扩展方法
trait SplitWhen: Iterator {
    fn split_when<F>(self, predicate: F) -> SplitIter<Self, F>
    where
        Self: Sized,
        F: Fn(&Self::Item) -> bool,
    {
        SplitIter::new(self, predicate)
    }
}

impl<I: Iterator> SplitWhen for I {}

// 使用这个自定义迭代器修改你的函数
use std::error::Error;

pub fn part_one(input: &str) -> Result<String, Box<dyn Error>> {
    let result = input.lines()
        .split_when(|line| line.is_empty())
        .map(|inventory_iter| {
            inventory_iter
                .filter_map(|item| item.parse::<i32>().ok())
                .sum::<i32>()
        })
        .max()
        .ok_or("Input was empty")?;

    Ok(result.to_string())
}

不过手动实现需要处理很多细节(比如跳过开头的拆分元素、子迭代器和父迭代器的状态同步等),不如直接使用itertools成熟的实现来得简洁可靠。

内容的提问来源于stack exchange,提问作者MaxCross

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:13:10