Rust Chars

語言: CN / TW / HK

練習題

codewars 上有一道題:

This time no story, no theory.

The examples below show you how to write function accum :

Examples:

accum("abcd") -> "A-Bb-Ccc-Dddd"
accum("RqaEzty") -> "R-Qq-Aaa-Eeee-Zzzzz-Tttttt-Yyyyyyy"
accum("cwAt") -> "C-Ww-Aaa-Tttt"

The parameter of accum is a string which includes only letters from a..z and A..Z .

要求實現 accum()

題目本身不難,大體思路是:對於入參,轉變成字元列表,然後對每個字元進行 repeat m 次,m就等於該字元在字串的index,( repeat 結果的首字母大寫),最後使用 - 將這些結果 join 起來就完成了。

對於 str 有個 chars() 方法,文件說明:

pub fn chars(&self) -> Chars<'_>

例如:

let word = "goodbye";
let mut chars = word.chars();
assert_eq!(Some('g'), chars.next());
assert_eq!(Some('o'), chars.next());
assert_eq!(Some('o'), chars.next());
assert_eq!(Some('d'), chars.next());
assert_eq!(Some('b'), chars.next());
assert_eq!(Some('y'), chars.next());
assert_eq!(Some('e'), chars.next());

assert_eq!(None, chars.next());

返回的 Chars 型別,是 str 的在其 chars 的迭代器。

接下來我們會對每個字元做 map 操作,但是我們同時需要每個字元在字串的index位置。這時候就要介紹一下 enumerate() 方法。在迭代資料的時候,同時給出當前的索引值。

fn enumerate(self) -> Enumerate<Self>

例如:

let a = ['a', 'b', 'c'];

let mut iter = a.iter().enumerate();

assert_eq!(iter.next(), Some((0, &'a')));
assert_eq!(iter.next(), Some((1, &'b')));

如何重複某個字元m次?標準庫裡有個 repeat() 方法:

let a= "a";
let b= std::iter::repeat(a).take(3).collect::<String>();
println!("result {}",b);

關於 join() 方法,先看看其在 slice 的定義:

pub fn join<Separator>(
    &self,
    sep: Separator
) -> <[T] as Join<Separator>>::Output
where
    [T]: Join<Separator>, 

slice T 轉變成使用 Separator 連線的 Output 型別的值。

Join 是個 trait 它的定義是

pub trait Join<Separator> {
    type Output;
    fn join(slice: &Self, sep: Separator) -> Self::Output;
}

其中定義了一個 Associated Type OutputJoin trait 自帶了幾個實現:

//使用 &str join [S],這個最常用
impl<'_, S> Join<&'_ str> for [S]
where
    S: Borrow<str>,

例如:

assert_eq!(["hello", "world"].join(" "), "hello world");
//使用 &T join [V]
impl<'_, T, V> Join<&'_ T> for [V]
where
    T: Clone,
    V: Borrow<[T]>, 

例如:

//這裡 T 與 V 是同一個type了
assert_eq!([[1, 2], [3, 4]].join(&0), [1, 2, 0, 3, 4]);
//使用 &[T] join [V]
impl<'_, T, V> Join<&'_ [T]> for [V]
where
    T: Clone,
    V: Borrow<[T]>, 

例如:

assert_eq!([[1, 2], [3, 4]].join(&[0, 0][..]), [1, 2, 0, 0, 3, 4]);

講完 slice 再講講 std::vev::Vecjoin() 方法。

嗯,就比較簡單了, std::vev::Vec 憑藉著 Methods from Deref<Target = [T]> Vec 直接就具備了 slice 的能力。

好了吧上面的步驟組合起來的到解決方案:

fn accum(s:&str)->String {
    s
    .to_lowercase()
    .chars()
    .enumerate()
    .map(|(i,e)| e.to_uppercase().to_string() + 
            &std::iter::repeat(e).take(i).collect::<String>() ) 
    .collect::<Vec<_>>()
    .join("-")
}

需要稍微解釋的是我們將大寫字母拼接上 repeat() 了 m-1(剛好是i)次的字元。而不是 repeat() m次。

類圖

於是趁著做這道題,就梳理一下 str , slice , String 等等型別之間的關係。