从字符串创建字符切片的滑动窗口迭代器

问题描述 投票:0回答:3

我正在寻找使用为

slices 提供的 
String
 函数从 
Windows<T>
windows 的最佳方法。

我明白了如何用这种方式使用Windows:

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 方法的字符切片。

string utf-8 rust iterator slice
3个回答
18
投票

您面临的问题是,

String
实际上被表示为类似
Vec<u8>
的东西,并且有一些API可以让您访问
char
。在 UTF-8 中,代码点的表示可以是 1 到 4 个字节之间的任何内容,并且它们都被压缩在一起以提高空间效率。

在不复制所有内容的情况下,您可以直接获得整个

String
的唯一切片将是
&[u8]
,但您不知道这些字节是否对应于整个或部分代码点。

char
类型与代码点完全对应,因此大小为 4 个字节,因此它可以容纳任何可能的值。因此,如果您通过从
char
复制来构建
String
的切片,结果可能会大 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 个
char

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));
}

16
投票

此解决方案将满足您的目的。 (游乐场)

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());
}

String 可以迭代其字符,但它不是切片,因此您必须将其收集到 vec 中,然后将其强制转换为切片。


9
投票

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

use itertools::Itertools; // 0.10.0

fn main() {
    let input = "日本語";

    for (a, b) in input.chars().tuple_windows() {
        println!("{}, {}", a, b);
    }
}

另请参阅:

© www.soinside.com 2019 - 2024. All rights reserved.