如何在 Rust 中将字符串拆分为块以插入空格

Posted

技术标签:

【中文标题】如何在 Rust 中将字符串拆分为块以插入空格【英文标题】:How to split string into chunks in Rust to insert spaces 【发布时间】:2019-11-23 13:54:35 【问题描述】:

我正在尝试学习 Rust。我最近遇到的一个问题是: 给定一个String,正好是 n 的某个倍数,我想将字符串拆分为大小为 n 的块,并在这些块之间插入一个空格,然后收集回单个字符串。

我遇到的问题是chars() 方法返回Chars 结构,由于某种原因它没有实现SliceConcatExt 特征,因此不能在其上调用chunks()

此外,一旦我成功创建了一个 Chunks 结构(通过调用 .bytes() 而不是),我不确定如何调用 .join(' '),因为元素现在是字节切片的 Chunks...

必须有一种优雅的方式来做到这一点,我错过了。

例如,这里有一个说明情况的输入/输出:

given: whatupmyname, 4
output: what upmy name

这是我写得不好的尝试:

let n = 4;
let text = "whatupmyname".into_string();
text.chars()
    // compiler error on chunks() call
    .chunks(n)
    .collect::<Vec<String>>()
    .join(' ')

感谢您的帮助!

【问题讨论】:

我不知道你所说的字符串的“大小”是什么。您可以将é(一个代码点,两个字节)分成多少块? (两个代码点,三个字节)呢? ????????(两个代码点,八个字节)呢? Creating a sliding window iterator of slices of chars from a String的可能重复 @trentcl 很公平,我想我应该指定在这种情况下我只担心 char 的 - 可以由这 128 个字节表示的字符。哪个更有限,但对于我的目的来说足够简单。 @hellow 这确实非常接近在字符串上创建滑动窗口,但我认为这种情况会有所不同,因为我试图创建块,然后将这些块收集到字符串中。我遇到了双方的问题:一旦将String转换为Vec,确实可以进行分块,但是收集仍然很麻烦。 【参考方案1】:

这里的问题是chars()bytes() 返回Iterators,而不是切片。你可以使用as_bytes(),它会给你一个&amp;[u8]。但是,您不能直接从&amp;str 中获取&amp;[char],因为只存在字节本身,并且必须通过查看每个字节组成的字节数来创建chars。你必须这样做:

text.chars()
    .collect::<Vec<char>>()
    .chunks(n)
    .map(|c| c.iter().collect::<String>())
    .collect::<Vec<String>>()
    .join(" ");

但是,我不建议这样做,因为它必须为Vecs 和Strings 分配大量临时存储空间。相反,你可以做这样的事情,它只需要分配来创建最终的String

text.chars()
    .enumerate()
    .flat_map(|(i, c)| 
        if i != 0 && i % n == 0 
            Some(' ')
         else 
            None
        
        .into_iter()
        .chain(std::iter::once(c))
    )
    .collect::<String>()

在最后一次收集之前,它一直作为迭代器,通过 flat_mapping 与一个迭代器进行映射,该迭代器要么是字符,要么是空格,然后是字符。

【讨论】:

我在发布之前尝试了第一个建议(虽然我对制作不必要的 Vec 感到不安)但我在collect::&lt;Vec&lt;String&gt;&gt;() 调用时遇到了编译器错误。有句话说不能从Iterator&lt;&amp;[char]&gt; 构建Vec&lt;String&gt;,这让我觉得很奇怪。编辑:现在运行它,它指出 FromIterator&lt;&amp;[char]&gt; 的特征没有为 Vec&lt;String&gt; 实现,所以我认为也许我可以实现该特征? 已修复。我不建议使用该代码,但它必须分配太多。顺便说一句,您将无法实现该特征,因为所涉及的特征和类型都不是“您的”。 哦,我明白了。我认为有一种方法可以将字符块隐式转换为字符串,但地图会为您做到这一点。此外,flat_map 概念对我来说有点陌生,但我将尝试解构它: - flat_map 通常会展平嵌套结构,但在这种情况下,它用于返回迭代器 - 如果你在第 n 个字符上,插入一个包含空格的迭代器,并将其链接到当前迭代器中,使其位于它之前。否则, None 将变成一个不会产生任何结果的迭代器。 - 将迭代器收集到一个字符串中【参考方案2】:

因此,如果您想从一个字符列表中创建一个字符串,您可以使用fold。

类似这样的:

text.chars
    .enumerate()
    .fold(String::new(), |acc, (i, c)| 
        if i != 0 && i == n 
            format!(" ", acc, c)
         else 
            format!("", acc, c)
        
    )

【讨论】:

哦,有趣,我也喜欢这个解决方案,感谢您花时间做出回应。这是一个非常清晰易懂的解决方案,我应该考虑过。谢谢!我很好奇与 JayDepp 发布的 flat_map 解决方案相比是否存在字符串分配开销。 所以,不幸的是,有。 format! 正在创建一个 String 并返回它。因此,对于每个字符,都会创建一个新的String,其中包含带有当前字符的前一个字符和一个可选的空格(如果需要)。最后,您将得到相同的String,但由于有多个中间String,因此会有相当多的开销。上述方法更好,因为您将拥有一个Iterator&lt;Iterator&lt;Char&gt;&gt;,而flat_map 将创建一个Iterator&lt;Char&gt;,在最后准备好成为collected。所以你只会创建一个最终的String【参考方案3】:

如果要拆分的数据大小是固定的,那么:

use std::str;

fn main() 
    let subs = "&#8204;&#8203;&#8204;&#8203;&#8204;&#8203;&#8203;&#8204;&#8203;&#8204;".as_bytes()
        .chunks(7)
        .map(str::from_utf8)
        .collect::<Result<Vec<&str>, _>>()
        .unwrap();
        
    println!(":?", subs);


// >> ["&#8204;", "&#8203;", "&#8204;", "&#8203;", "&#8204;", "&#8203;", "&#8203;", "&#8204;", "&#8203;", "&#8204;"]

【讨论】:

以上是关于如何在 Rust 中将字符串拆分为块以插入空格的主要内容,如果未能解决你的问题,请参考以下文章

如何在 bash shell 中将一个字符串拆分为多个字符串,至少用一个空格分隔?

在 Swift 3 中将数据拆分为块

如何在批处理脚本中将字符串拆分为 3 个不同的字符串?

在 Swift 中将字符串拆分为数组?

Sql中将字符串按分割符拆分

如何在awk中将分隔字符串拆分为数组?