rust - 如何就地洗牌

标签 rust

我想在 Rust 中就地洗牌一个字符串,但我似乎遗漏了什么。修复可能是微不足道的......

use std::rand::{Rng, thread_rng};

fn main() {
    // I want to shuffle this string...
    let mut value: String = "SomeValue".to_string();
    let mut bytes = value.as_bytes();
    let mut slice: &mut [u8] = bytes.as_mut_slice();

    thread_rng().shuffle(slice);

    println!("{}", value); 
}

我得到的错误是

<anon>:8:36: 8:41 error: cannot borrow immutable dereference of `&`-pointer `*bytes` as mutable
<anon>:8         let mut slice: &mut [u8] = bytes.as_mut_slice();
                                            ^~~~~

我读到了有关 String::as_mut_vec() 的内容,但它不安全,所以我不想使用它。

最佳答案

没有很好的方法来做到这一点,部分原因是字符串的 UTF-8 编码的性质,部分原因是 Unicode 和文本的固有属性。

在 UTF-8 字符串中至少有三层可以打乱:

  • 原始字节
  • 编码的代码点
  • 字素

打乱原始字节可能会给出无效的 UTF-8 字符串作为输出,除非该字符串完全是 ASCII。非 ASCII 字符被编码为多字节的特殊序列,对这些字符进行混洗几乎肯定不会在最后以正确的顺序排列它们。因此,改组字节通常不好。

改组代码点(Rust 中的 char)更有意义,但仍然存在“特殊序列”的概念,其中所谓的 combining characters可以叠加到单个字母上并添加变音符号等(例如 ä 之类的字母可以写成 a 加上 U+0308,代码点表示 the diaeresis )。因此,打乱字符不会产生无效的 UTF-8 字符串,但它可能会破坏这些代码点序列并产生无意义的输出。

这让我想到字素:构成单个可见字符的代码点序列(如 ä 在写为一个或两个代码点时仍然是单个字素)。这将给出最可靠、最明智的答案。

然后,一旦您决定要洗牌,就可以制定洗牌策略:

  • 如果保证字符串是纯 ASCII,则用 .shuffle 打乱字节是合理的(在 ASCII 假设下,这等同于其他)
  • 否则,没有就地操作的标准方法,可以将元素作为迭代器(.chars() 用于代码点,.graphemes(true) 用于字素),将它们放入带有 .collect::<Vec<_>>() 的向量中,打乱向量,然后将所有内容收集回一个新的 String与例如.iter().map(|x| *x).collect::<String>() .

处理代码点和字素的困难是因为 UTF-8 不会将它们编码为固定宽度,因此无法取出随机代码点/字素并将其插入其他地方,或者以其他方式有效地交换两个元素......无需将所有内容解码为外部 Vec .

不在原地是不幸的,但字符串很难。

(如果您的字符串保证是 ASCII,那么使用 Ascii 提供的 ascii 之类的类型将是在类型级别保持直截了当的好方法。)


作为这三者的区别的一个例子,看一下:

fn main() {
    let s = "U͍̤͕̜̲̼̜n̹͉̭͜ͅi̷̪c̠͍̖̻o̸̯̖de̮̻͍̤";
    println!("bytes: {}", s.bytes().count());
    println!("chars: {}", s.chars().count());
    println!("graphemes: {}", s.graphemes(true).count());
}

它打印:

bytes: 57
chars: 32
graphemes: 7

( Generate your own , 演示将多个组合字符放在一个字母上。)

关于rust - 如何就地洗牌,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27816488/

相关文章:

rust - 返回对 Rc<RefCell<_>> 内容的引用时的生命周期

rust - 将本地值插入静态 mut 的安全方法

string - 为什么从标准输入读取用户输入时我的字符串不匹配?

closures - 使用闭包创建回调系统

rust - 为什么 Option<f64> 的大小在 64 位 Linux 上是 16 字节?

rust - 使用 nom 进行解析时,无法推断在函数 `I` 上声明的类型参数 `tuple` 的类型

rust - `.map(f)` 和 `.map(|x| f(x))` 有什么区别?

reference - 构建具有特定生命周期的对象

rust - 对于数组长度,类似于 C 预处理器的 #define 是什么?

rust - 在默认特征实现中使用关联常量