从字符串中创建一个字符片的滑动窗口迭代器



我正在寻找使用为切片提供的windows函数从StringWindows<T>的最佳方法。

我了解如何以这种方式使用窗口:

fn main() {
let tst = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let mut windows = tst.windows(3);
// prints ['a', 'b', 'c']
println!("{:?}", windows.next().unwrap());
// prints ['b', 'c', 'd']
println!("{:?}", windows.next().unwrap());
// etc...
}

但我在处理这个问题时有点不知所措:

fn main() {
let tst = String::from("abcdefg");
let inter = ? //somehow create slice of character from tst
let mut windows = inter.windows(3);
// prints ['a', 'b', 'c']
println!("{:?}", windows.next().unwrap());
// prints ['b', 'c', 'd']
println!("{:?}", windows.next().unwrap());
// etc...
}

从本质上讲,我正在寻找如何将字符串转换为可以使用window方法的char切片。

您面临的问题是,String实际上被表示为类似于引擎盖下的Vec<u8>,有一些API可以让您访问chars。在UTF-8中,代码点的表示可以是1到4个字节,为了提高空间效率,它们都被压缩在一起。

在不复制所有内容的情况下,您可以直接获得整个String的唯一切片是&[u8],但您不知道字节是对应于代码点的全部还是部分。

char类型正好对应于一个代码点,因此其大小为4字节,因此可以容纳任何可能的值。因此,如果通过从String复制来构建char的切片,结果可能会大4倍。

为了避免进行潜在的大的临时内存分配,您应该考虑一种更懒惰的方法——遍历String,在char边界上进行切片。类似这样的东西:

fn char_windows<'a>(src: &'a str, win_size: usize) -> impl Iterator<Item = &'a str> {
src.char_indices()
.flat_map(move |(from, _)| {
src[from ..].char_indices()
.skip(win_size - 1)
.next()
.map(|(to, c)| {
&src[from .. from + to + c.len_utf8()]
})
})
}

这将为您提供一个迭代器,其中项目为&str,每个项目有3个chars:

let mut windows = char_windows(&tst, 3);
for win in windows {
println!("{:?}", win);
}

这种方法的好处是它根本没有进行任何复制——迭代器生成的每个&str仍然是原始源String的一个切片。


所有这些复杂性都是因为Rust默认情况下对字符串使用UTF-8编码。如果绝对知道输入字符串不包含任何多字节字符,则可以将其视为ASCII字节,切片变得很容易:

let tst = String::from("abcdefg");
let inter = tst.as_bytes();
let mut windows = inter.windows(3);

然而,您现在有了字节片,您需要将它们转换回字符串来对它们执行任何操作:

for win in windows {
println!("{:?}", String::from_utf8_lossy(win));
}

此解决方案将适用于您的目的。(操场(

fn main() {
let tst = String::from("abcdefg");
let inter = tst.chars().collect::<Vec<char>>();
let mut windows = inter.windows(3);
// prints ['a', 'b', 'c']
println!("{:?}", windows.next().unwrap());
// prints ['b', 'c', 'd']
println!("{:?}", windows.next().unwrap());
// etc...
println!("{:?}", windows.next().unwrap());
}

字符串可以对其字符进行迭代,但它不是一个切片,所以必须将它收集到一个vec中,然后将其强制为一个切片。

您可以使用itertools遍历任何迭代器的窗口,最大宽度为4:

extern crate itertools; // 0.7.8
use itertools::Itertools;
fn main() {
let input = "日本語";
for (a, b) in input.chars().tuple_windows() {
println!("{}, {}", a, b);
}
}

另请参阅:

  • 迭代器是否有slice::chunks/windows的等价物来循环成对、三元组等

最新更新