如何用nom优化多空白字符替换为单个空格的Rust实现?
改进方案
1. 用nom批量读取非空白字符,减少迭代开销
你原来用anychar逐个处理非空白字符的方式,不仅代码繁琐还存在不必要的性能开销。可以利用take_till1(is_multispace)批量读取连续非空白段,搭配multispace1读取空白段,每次处理一段内容而非单个字符,代码更简洁高效:
use nom::{ character::is_multispace, combinator::{map, value}, multi::many0, sequence::alt, IResult, }; fn replace_multispaces(i: &str) -> IResult<&str, String> { let (rest, parts) = many0(alt(( // 匹配连续空白,直接映射为单个空格 value(" ".to_string(), multispace1), // 匹配连续非空白字符段,直接转为String map(take_till1(is_multispace), |s: &str| s.to_string()), )))(i)?; Ok((rest, parts.into_iter().collect())) }
这里用value将multispace1的匹配结果直接替换为单个空格的字符串,take_till1(is_multispace)一次性读取所有连续非空白字符,避免了逐个字符处理的冗余操作。
2. 复用nom的空白判断逻辑,直接用字符串处理
如果不需要严格遵循nom的解析器流程,直接用Rust原生字符串API结合nom的is_multispace函数会更轻量,性能也更优:
use nom::character::is_multispace; fn replace_multispaces(s: &str) -> String { let mut result = String::with_capacity(s.len()); let mut in_space = false; for c in s.chars() { if is_multispace(c) { if !in_space { result.push(' '); in_space = true; } } else { result.push(c); in_space = false; } } result }
这个方法直接遍历字符,用is_multispace判断空白类型,同时跟踪是否处于连续空白段,只保留一个空格——既复用了nom的空白判断逻辑,又不需要启动nom的解析器组合流程,代码更加简洁直接。
3. 优化原实现的冗余点(若坚持保留原思路)
如果你想沿用many0+alt的结构,可以去掉自定义枚举,改用nom提供的Either类型,同时添加状态跟踪避免连续空格,并用预分配内存减少扩容开销:
use nom::{ branch::Either, character::complete::{anychar, multispace1}, combinator::map, multi::many0, IResult, }; fn replace_multispaces(i: &str) -> IResult<&str, String> { let (rest, tokens) = many0(alt(( map(multispace1, |_| Either::Left(())), map(anychar, |c| Either::Right(c)), )))(i)?; let mut result = String::with_capacity(i.len()); let mut last_was_space = false; for token in tokens { match token { Either::Left(_) => { if !last_was_space { result.push(' '); last_was_space = true; } } Either::Right(c) => { result.push(c); last_was_space = false; } } } Ok((rest, result)) }
内容的提问来源于stack exchange,提问作者exocortex
相关产品推荐
相关产品推荐

