rust 从字符串中删除多余空格的理想方法是什么?

btxsgosb  于 2022-11-12  发布在  其他
关注(0)|答案(2)|浏览(471)

我正在处理字符串,需要用一个空格替换多个空格。看起来大多数都是人为错误,但我对处理这个问题的理想方法很好奇--最好是从&strString的分配最少。
到目前为止这是我下面的做法:

const SPACE: &str = " ";
const TWO_SPACES: &str = "  ";

/// Replace multiple spaces with a single space
pub fn trim_whitespace(s: &str) -> String {
    let mut new_str: String = s.trim().to_owned();
    while new_str.contains(TWO_SPACES) {
        new_str = new_str.replace(TWO_SPACES, SPACE);
    }
    new_str
}

let result = trim_whitespace("Hello     world! ");
assert_eq!(result, "Hello world!");
  • 编辑(2022年10月)*:我的背景是Python,在Python中做类似上面的事情是非常习惯的。例如,Python中的fastest version(用一个空格替换多个空格)看起来是这样的:
def trim_whitespace(s: str) -> str:
    s = s.strip()
    while '  ' in s:
        s = s.replace('  ', ' ')
    return s
kokeuurv

kokeuurv1#

split_whitespace()对于这种用法非常方便。
在非常简单的第一个解决方案中分配了一个向量和一个字符串。
第二种解决方案只分配一个字符串,但有点不优雅(每次迭代都是if)。

pub fn trim_whitespace_v1(s: &str) -> String {
    // first attempt: allocates a vector and a string
    let words: Vec<_> = s.split_whitespace().collect();
    words.join(" ")
}

pub fn trim_whitespace_v2(s: &str) -> String {
    // second attempt: only allocate a string
    let mut result = String::with_capacity(s.len());
    s.split_whitespace().for_each(|w| {
        if !result.is_empty() {
            result.push(' ');
        }
        result.push_str(w);
    });
    result
}

fn main() {
    let source = "  a   bb cc   ddd    ";
    println!("{:?}", trim_whitespace_v1(source)); // "a bb cc ddd"
    println!("{:?}", trim_whitespace_v2(source)); // "a bb cc ddd"
}
fae0ux8s

fae0ux8s2#

你可以使用split(' '),过滤掉空的条目,然后用空格重新连接:

s.trim()
    .split(' ')
    .filter(|s| !s.is_empty())
    .collect::<Vec<_>>()
    .join(" ")

// Or, using itertools:
use itertools::Itertools;
s.trim().split(' ').filter(|s| !s.is_empty()).join(" ")

另一种可能性是使用String::retain()并删除连续的空格。它也应该更快,因为它只分配一次,为修剪的字符串:

pub fn trim_whitespace(s: &str) -> String {
    let mut new_str = s.trim().to_owned();
    let mut prev = ' '; // The initial value doesn't really matter
    new_str.retain(|ch| {
        let result = ch != ' ' || prev != ' ';
        prev = ch;
        result
    });
    new_str
}

**编辑:**我很好奇,所以我用字符串" a bb cc ddd "对这里建议的所有版本进行了基准测试(当然,不同的字符串会有不同的性能特征)。基准测试代码是here(需要criterionitertools)。

结果:

benches/trim_whitespace_replace
                        time:   [846.02 ns 872.71 ns 901.90 ns]
benches/trim_whitespace_retain
                        time:   [146.79 ns 153.07 ns 159.91 ns]
benches/trim_whitespace_split_space
                        time:   [268.61 ns 277.44 ns 287.55 ns]
benches/trim_whitespace_split_space_itertools
                        time:   [392.82 ns 406.92 ns 423.88 ns]
benches/trim_whitespace_split_whitespace
                        time:   [236.38 ns 244.51 ns 254.00 ns]
benches/trim_whitespace_split_whitespace_itertools
                        time:   [395.82 ns 413.59 ns 433.26 ns]
benches/trim_whitespace_split_whitespace_only_one_string
                        time:   [146.25 ns 152.73 ns 159.94 ns]

不出所料,您使用replace()的版本是最慢的。我使用retain()和@prog-fh的更快版本是最快的(我期望他的版本会更快,因为它需要复制的更少,但显然差异非常小,现代CPU复制小块内存的速度非常快。也许在更大的字符串中会出现这种情况)。有点令人惊讶的是,使用itertools的join()的版本比只使用标准库collect()join()的版本要慢,尽管不需要首先收集到一个向量中。(我不确定,但需要检查程序集来验证),更糟糕的是,他们实际上可能需要分配更多的空间,因为他们不知道提前需要的空间量,而且他们还需要插入分隔符。

相关问题