String 如何交换字符串中的两个字符?

String 如何交换字符串中的两个字符?,string,rust,String,Rust,我想写一个函数,如下所示: 输入:字符串A,整数i,0CharRange 给定字节位置和str,返回上一个字符及其位置 这两种方法结合在一起,让我们可以前后查看字符串,这正是我们想要的 但是等等,还有更多!DK指出了一个带有上述代码的角落案例。如果输入包含任何字符,它们可能会与它们组合的字符分离 现在,这个问题是关于生锈的,而不是Unicode,所以我不会详细讨论它。您现在需要知道的是,锈迹能够: fn图形索引(&self,扩展为:bool)->图形索引 返回对self及其字节偏移

我想写一个函数,如下所示:

  • 输入:字符串A,整数i,0
  • 输出:字符位于(i-1)的字符串A与字符位于i的字符串A交换
实现这一目标的清洁解决方案是什么?我目前的解决办法是:

let mut swapped = input_str[0..i].to_string();
swapped.push(input_str.char_at(i));
swapped.push(input_str.char_at(i - 1));
swapped.push_str(&query[i..input_str.len()]);

但这只适用于ASCII字符串。我可以将其他解决方案想象为在UTF-32中转换为向量,在那里交换并转换回字符串,但这似乎需要很多额外的工作。

这里有一个很好的解决方案:

use std::str::CharRange;

fn swap_chars_at(input_str: &str, i: usize) -> String {
    // Pre-allocate a string of the correct size
    let mut swapped = String::with_capacity(input_str.len());
    // Pluck the previous character
    let CharRange { ch: prev_ch, next: prev } = input_str.char_range_at_reverse(i);
    // Pluck the current character
    let CharRange { ch, next } = input_str.char_range_at(i);
    // Put them back
    swapped.push_str(&input_str[..prev]);
    swapped.push(ch);
    swapped.push(prev_ch);
    swapped.push_str(&input_str[next..]);
    // Done!
    swapped
}

#[test]
fn smoke_test() {
    let s = swap_chars_at("lyra", 2);
    assert_eq!(s, "lrya");
}

#[test]
fn unicode() {
    // 'ç' takes up 2 bytes in UTF-8
    let s = swap_chars_at("ça va?", 2);
    assert_eq!(s, "aç va?");
}
#![allow(unstable)]  // `GraphemeIndices` is unstable

fn swap_graphemes_at(input_str: &str, i: usize) -> String {
    // Pre-allocate a string of the correct size
    let mut swapped = String::with_capacity(input_str.len());
    // Find the grapheme at index i
    let (_, gr) = input_str.grapheme_indices(true)
        .find(|&(index, _)| index == i)
        .expect("index does not point to a valid grapheme");
    // Find the grapheme just before it
    let (prev, prev_gr) = input_str.grapheme_indices(true).rev()
        .find(|&(index, _)| index < i)
        .expect("no graphemes to swap with");
    // Put it all back together
    swapped.push_str(&input_str[..prev]);
    swapped.push_str(gr);
    swapped.push_str(prev_gr);
    swapped.push_str(&input_str[i+gr.len()..]);
    // Done!
    swapped
}

#[test]
fn combining() {
    // Ensure that "c\u{327}" is treated as a single unit
    let s = swap_graphemes_at("c\u{327}a va?", 3);
    assert_eq!(s, "ac\u{327} va?");
}
从:

  • fn char\u range\u at(&self,start:usize)->CharRange
    • 从字符串中提取一个字符并返回下一个字符的索引
  • fn char\u range\u反方向(&self,start:usize)->CharRange
    • 给定字节位置和str,返回上一个字符及其位置
这两种方法结合在一起,让我们可以前后查看字符串,这正是我们想要的


但是等等,还有更多!DK指出了一个带有上述代码的角落案例。如果输入包含任何字符,它们可能会与它们组合的字符分离

现在,这个问题是关于生锈的,而不是Unicode,所以我不会详细讨论它。您现在需要知道的是,锈迹能够:

  • fn图形索引(&self,扩展为:bool)->图形索引
    • 返回对self及其字节偏移量的迭代
通过对
.find()
.rev()
的健康应用,我们(希望)得出了这个正确的解决方案:

use std::str::CharRange;

fn swap_chars_at(input_str: &str, i: usize) -> String {
    // Pre-allocate a string of the correct size
    let mut swapped = String::with_capacity(input_str.len());
    // Pluck the previous character
    let CharRange { ch: prev_ch, next: prev } = input_str.char_range_at_reverse(i);
    // Pluck the current character
    let CharRange { ch, next } = input_str.char_range_at(i);
    // Put them back
    swapped.push_str(&input_str[..prev]);
    swapped.push(ch);
    swapped.push(prev_ch);
    swapped.push_str(&input_str[next..]);
    // Done!
    swapped
}

#[test]
fn smoke_test() {
    let s = swap_chars_at("lyra", 2);
    assert_eq!(s, "lrya");
}

#[test]
fn unicode() {
    // 'ç' takes up 2 bytes in UTF-8
    let s = swap_chars_at("ça va?", 2);
    assert_eq!(s, "aç va?");
}
#![allow(unstable)]  // `GraphemeIndices` is unstable

fn swap_graphemes_at(input_str: &str, i: usize) -> String {
    // Pre-allocate a string of the correct size
    let mut swapped = String::with_capacity(input_str.len());
    // Find the grapheme at index i
    let (_, gr) = input_str.grapheme_indices(true)
        .find(|&(index, _)| index == i)
        .expect("index does not point to a valid grapheme");
    // Find the grapheme just before it
    let (prev, prev_gr) = input_str.grapheme_indices(true).rev()
        .find(|&(index, _)| index < i)
        .expect("no graphemes to swap with");
    // Put it all back together
    swapped.push_str(&input_str[..prev]);
    swapped.push_str(gr);
    swapped.push_str(prev_gr);
    swapped.push_str(&input_str[i+gr.len()..]);
    // Done!
    swapped
}

#[test]
fn combining() {
    // Ensure that "c\u{327}" is treated as a single unit
    let s = swap_graphemes_at("c\u{327}a va?", 3);
    assert_eq!(s, "ac\u{327} va?");
}
#![允许(不稳定)]/`GraphemeIndices`不稳定
fn swap_graphemes_at(input_str:&str,i:usize)->String{
//预先分配一个大小正确的字符串
让mut swapped=String::具有_容量(input_str.len());
//在索引i处查找grapheme
let(u,gr)=输入图素索引(真)
.find(|&(index,|)index==i)
.expect(“索引未指向有效的字符名”);
//在它之前找到我
let(prev,prev_gr)=输入图素索引(true).rev()
.find(|和(索引,|)索引
无可否认,这有点复杂。首先,它遍历输入,从
i
中取出grapheme集群。然后它通过输入向后迭代(),选择索引为
的最右边的集群(即前一个集群)。最后,它把所有的东西重新组合起来

如果你真的很迂腐,还有更多的特殊情况需要处理。例如,如果字符串包含Windows换行符(
“\r\n”
),那么我们可能不想交换它们。在希腊语中,当字母sigma(σ)位于单词(ς)的末尾时,它的书写方式不同,因此需要更好的算法在它们之间进行转换。别忘了那些


但是为了我们的理智起见,我们就到此为止。

我认为你的代码中有一个错误——如果我读对了,它会将字符推到
I
I-1
两次。我不相信这是正确的:你处理的是代码点,而不是图形,这是你应该做的。也就是说,如果您有组合代码点,它们将不会与您正在交换的代码点交换。@DK很好。我本来想说标准库没有grapheme拆分器,但事实证明它有。注释和编辑。令人印象深刻的编辑。这里真正的问题是,最初的问题是完全错误的:如果你做的任何事情都依赖于一个“角色”的具体概念,那么你可能只是在给自己制造麻烦。要做到正确是非常困难的。