Как поменять местами два символа в строке?

Я хочу написать функцию следующим образом:

  • вход: строка 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

С здоровым применением .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-->двунаправленный управляющие символы...