pub trait StringReverse {
#[must_use]
fn reverse_str(&self) -> String;
#[must_use]
fn reverse_delimited(&self, separator: char) -> String;
#[must_use]
fn rotate(&self, shift: i32) -> String;
}
impl StringReverse for str {
fn reverse_str(&self) -> String {
self.chars().rev().collect()
}
fn reverse_delimited(&self, separator: char) -> String {
if self.is_empty() {
return String::new();
}
self.split(separator)
.rev()
.collect::<Vec<_>>()
.join(&separator.to_string())
}
fn rotate(&self, shift: i32) -> String {
let char_count = self.chars().count();
if char_count == 0 || shift == 0 {
return self.to_string();
}
let effective = shift.rem_euclid(char_count as i32) as usize;
if effective == 0 {
return self.to_string();
}
let split_point = char_count - effective;
let byte_index = self
.char_indices()
.nth(split_point)
.map(|(i, _)| i)
.unwrap_or(self.len());
let mut result = String::with_capacity(self.len());
result.push_str(&self[byte_index..]);
result.push_str(&self[..byte_index]);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
mod reverse_str {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".reverse_str(), "");
}
#[test]
fn single_char() {
assert_eq!("a".reverse_str(), "a");
}
#[test]
fn simple_word() {
assert_eq!("bat".reverse_str(), "tab");
}
#[test]
fn longer_word() {
assert_eq!("sdrawkcab".reverse_str(), "backwards");
}
#[test]
fn palindrome() {
assert_eq!("racecar".reverse_str(), "racecar");
}
#[test]
fn unicode() {
assert_eq!("café".reverse_str(), "éfac");
}
#[test]
fn with_spaces() {
assert_eq!("hello world".reverse_str(), "dlrow olleh");
}
}
mod reverse_delimited {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".reverse_delimited('.'), "");
}
#[test]
fn no_delimiter_found() {
assert_eq!("a b c".reverse_delimited('.'), "a b c");
}
#[test]
fn dot_delimiter() {
assert_eq!("a.b.c".reverse_delimited('.'), "c.b.a");
}
#[test]
fn web_address() {
assert_eq!("www.domain.com".reverse_delimited('.'), "com.domain.www");
}
#[test]
fn single_segment() {
assert_eq!("abc".reverse_delimited('.'), "abc");
}
#[test]
fn slash_delimiter() {
assert_eq!("a/b/c".reverse_delimited('/'), "c/b/a");
}
#[test]
fn trailing_delimiter() {
assert_eq!("a.b.".reverse_delimited('.'), ".b.a");
}
#[test]
fn leading_delimiter() {
assert_eq!(".a.b".reverse_delimited('.'), "b.a.");
}
}
mod rotate {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".rotate(1), "");
}
#[test]
fn zero_shift() {
assert_eq!("abcdefg".rotate(0), "abcdefg");
}
#[test]
fn positive_shift() {
assert_eq!("abcdefg".rotate(2), "fgabcde");
}
#[test]
fn negative_shift() {
assert_eq!("abcdefg".rotate(-2), "cdefgab");
}
#[test]
fn full_rotation() {
assert_eq!("abcdefg".rotate(7), "abcdefg");
}
#[test]
fn negative_full_rotation() {
assert_eq!("abcdefg".rotate(-7), "abcdefg");
}
#[test]
fn shift_greater_than_length() {
assert_eq!("abcdefg".rotate(9), "fgabcde");
}
#[test]
fn negative_shift_greater_than_length() {
assert_eq!("abcdefg".rotate(-9), "cdefgab");
}
#[test]
fn large_positive_shift() {
assert_eq!("abcdefg".rotate(17), "efgabcd");
}
#[test]
fn large_negative_shift() {
assert_eq!("abcdefg".rotate(-17), "defgabc");
}
#[test]
fn single_char() {
assert_eq!("a".rotate(5), "a");
}
#[test]
fn unicode() {
assert_eq!("café".rotate(1), "écaf");
}
}
mod string_types {
use super::*;
#[test]
fn string_type_reverse() {
assert_eq!(String::from("bat").reverse_str(), "tab");
}
#[test]
fn string_type_rotate() {
assert_eq!(String::from("abcdefg").rotate(2), "fgabcde");
}
#[test]
fn boxed_str() {
let s: Box<str> = "a.b.c".into();
assert_eq!(s.reverse_delimited('.'), "c.b.a");
}
}
}