Как поменять местами два символа в строке?
Я хочу написать функцию следующим образом:
- вход: строка A, int i, 0
- Output: строка A с символом at (i - 1) заменена символом на i.
что это очистить решение, которое достигнет этого? Мое текущее решение:
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, замена там и преобразование обратно в стринг, но, похоже, много дополнительной работы.
1 ответов
вот довольно решение:
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?");
}
С документация:
-
fn char_range_at(&self, start: usize) -> CharRange
- извлеките символ из строки и верните индекс следующего символа.
-
fn char_range_at_reverse(&self, start: usize) -> CharRange
- учитывая позицию байта и str, верните предыдущий символ и его позицию.
вместе, эти два метода давайте заглянем назад и вперед в струне-это именно то, чего мы хотим.
Но подождите, есть больше! ДК указал на угловой кейс с указанным выше кодом. Если входные данные содержат любые сочетания символов, они могут отделиться от символов, с которыми они объединяются.
теперь этот вопрос касается Rust, а не Unicode, поэтому я не буду вдаваться в подробности как именно это работает. Все, что вам нужно знать сейчас, это то, что Rust обеспечивает этот метод:
-
fn grapheme_indices(&self, is_extended: bool) -> GraphemeIndices
- возвращает iterator по графема кластеров собственной личности и их смещений байта.
С здоровым применением .find()
и .rev()
, мы приходим к этому (надеюсь) правильному решению:
#![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?");
}
по общему признанию, это немного запутанно. Сначала он перебирает входные данные, выдергивая кластер графем в i
. Затем он повторяется назад (.rev()
) через вход, выбирая самый правый кластер с индексом < i
(т. е. предыдущий кластер). В конце концов, все встает на свои места.
если ты действительно педантичный, есть еще более специальные случаи, чтобы иметь дело. Например, если строка содержит новые строки Windows ("\r\n"
), тогда мы, вероятно, не хотим их менять. А по-гречески буква Сигма (σ) пишется по-разному когда она в конце слова (ς), поэтому лучше алгоритм должен переводить между ними по мере необходимости. И не забудь про них!--67-->двунаправленный управляющие символы...