从字符串中 trim 额外空格的理想方法是什么?
What's the ideal way to trim extra spaces from a string?
我正在处理需要用单个 space
替换多个 spaces
的字符串。看起来其中大部分只是人为错误,但我很好奇处理这个问题的理想方式——最好是从 &str
到 String
.
的最少分配
到目前为止,这是我的以下方法:
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!");
您可以使用 split(' ')
,过滤掉空条目,然后 re-join 通过 space:
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()
并删除连续的 space。它也应该更快,因为它只为修剪后的字符串分配一次:
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(需要 criterion
和 itertools
)。
结果:
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()
的版本慢,尽管不需要先收集到向量中。不过我可以解释一下——这个版本在 Display
上使用动态调度,编译器可能无法消除(不过我不确定,需要检查程序集来验证),更糟糕的是,他们可能实际上需要分配更多,因为他们不知道提前需要多少space,他们还需要插入分隔符。
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"
}
我正在处理需要用单个 space
替换多个 spaces
的字符串。看起来其中大部分只是人为错误,但我很好奇处理这个问题的理想方式——最好是从 &str
到 String
.
到目前为止,这是我的以下方法:
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!");
您可以使用 split(' ')
,过滤掉空条目,然后 re-join 通过 space:
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()
并删除连续的 space。它也应该更快,因为它只为修剪后的字符串分配一次:
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(需要 criterion
和 itertools
)。
结果:
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()
的版本慢,尽管不需要先收集到向量中。不过我可以解释一下——这个版本在 Display
上使用动态调度,编译器可能无法消除(不过我不确定,需要检查程序集来验证),更糟糕的是,他们可能实际上需要分配更多,因为他们不知道提前需要多少space,他们还需要插入分隔符。
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"
}