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

15

我正在寻找从StringWindows<T>的最佳方法,使用提供给sliceswindows函数。

我知道如何这样使用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...
}
基本上,我正在寻找如何将字符串转换为字符切片,以便我可以使用窗口方法。
3个回答

18
您面临的问题是,String在底层实际上被表示为类似于Vec<u8>的东西,并具有一些API可以让您访问char。在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()]
                })
    })
}

这将为您提供一个迭代器,其中每个项目都是包含3个 char&str

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

2
这是一个关于引擎内部运行机制的绝佳解释 - 感谢您阐明了这一点! - bmartin
这是一个带有char_windows的游乐场链接,并进行了轻微修改,以展示与Chunks等效的方法。 - Charles German

15

这个解决方案将适合您的目的。(playground

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中,然后将其强制转换为切片。


9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接