Here's a pretty solution:
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 }
From the documentation :
fn char_range_at(&self, start: usize) -> CharRange- Cross out the character from the string and return the index of the next character.
fn char_range_at_reverse(&self, start: usize) -> CharRange- Given the position of the byte and str, return the previous char and its position.
Together, these two methods will allow us to look back and forth into a string - this is exactly what we want.
But wait, there still! DK pointed to the corner case with the above code. If the input contains any combination of characters , they can be separated from the characters with which they are combined.
Now this question is about Rust, not Unicode, so I won’t go into details of how this works . All you need to know is that Rust provides this method :
fn grapheme_indices(&self, is_extended: bool) -> GraphemeIndices
With healthy use of .find() and .rev() we will come to this (hopefully) correct solution:
#![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 }
Admittedly, this is a bit confusing. First, it iterates through the input, plucking the grapheme cluster at i . Then it .rev() backward ( .rev() ) through the input, choosing the rightmost cluster with index < i (i.e., Previous Cluster). In the end, he goes and puts everything together.
If you are truly pedantic, there are even more special cases. For example, if a line contains Windows newline lines ( "\r\n" ), we probably will not want to change them. And in Greek, the letter sigma (σ) is written differently when it is at the end of the word (ς), so the best algorithm should translate between them as necessary. And don't forget those bi-directional control characters ...
But for the sake of our sanity, we will stop here.