这里有一个优美的解决方案:
use std::str::CharRange;
fn swap_chars_at(input_str: &str, i: usize) -> String {
let mut swapped = String::with_capacity(input_str.len());
let CharRange { ch: prev_ch, next: prev } = input_str.char_range_at_reverse(i);
let CharRange { ch, next } = input_str.char_range_at(i);
swapped.push_str(&input_str[..prev]);
swapped.push(ch);
swapped.push(prev_ch);
swapped.push_str(&input_str[next..]);
swapped
}
#[test]
fn smoke_test() {
let s = swap_chars_at("lyra", 2);
assert_eq!(s, "lrya");
}
#[test]
fn unicode() {
let s = swap_chars_at("ça va?", 2);
assert_eq!(s, "aç va?");
}
从
文档中得知:
fn char_range_at(&self, start: usize) -> CharRange
fn char_range_at_reverse(&self, start: usize) -> CharRange
- 给定一个字节位置和一个字符串,返回前一个字符及其位置。
这两种方法一起使用可以让我们向前或向后查看字符串——这正是我们想要的。
但等等,还有更多!DK指出了上面代码的一个特殊情况。如果输入包含任何
组合字符,它们可能会与它们结合的字符分离。
现在,这个问题是关于Rust而不是Unicode的,所以我不会详细介绍
具体如何工作。你现在需要知道的是,Rust提供了
此方法:
fn grapheme_indices(&self, is_extended: bool) -> GraphemeIndices
通过大量使用
.find()
和
.rev()
,我们得到了这个(希望是)正确的解决方案:
#![allow(unstable)]
fn swap_graphemes_at(input_str: &str, i: usize) -> String {
let mut swapped = String::with_capacity(input_str.len());
let (_, gr) = input_str.grapheme_indices(true)
.find(|&(index, _)| index == i)
.expect("index does not point to a valid grapheme");
let (prev, prev_gr) = input_str.grapheme_indices(true).rev()
.find(|&(index, _)| index < i)
.expect("no graphemes to swap with");
swapped.push_str(&input_str[..prev]);
swapped.push_str(gr);
swapped.push_str(prev_gr);
swapped.push_str(&input_str[i+gr.len()..]);
swapped
}
#[test]
fn combining() {
let s = swap_graphemes_at("c\u{327}a va?", 3);
assert_eq!(s, "ac\u{327} va?");
}
诚然,这有点复杂。首先,它遍历输入,在位置
提取出字形簇。然后,它向后迭代(
.rev()
)通过输入,在索引
< i
处选择最右侧的簇(即上一个簇)。最后,它将所有内容重新组合在一起。
如果你非常严谨,还有更多特殊情况需要处理。例如,如果字符串包含Windows换行符(
"\r\n"
),那么我们可能不想交换它们。在希腊语中,当字母西格玛(σ)位于单词末尾时,它的书写方式不同(ς),因此更好的算法应根据需要在它们之间进行转换。别忘了那些
双向控制字符...
但为了我们的理智,我们就到这里吧。
i
和i-1
处的字符推入两次。 - Lambda Fairy