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
相关产品推荐
相关产品推荐

