とある共通のprefixをStringのVecから抽出するプログラムで、以下のような美しいソースコードを見たのですが、一目見て理解できない点が多かったので、自分の理解した範囲でメモ書き程度に記録しておこうと思います。
pub fn hoge(strs: Vec<String>) -> String {
let mut strs = strs.iter();
if let Some(head) = strs.next().cloned() { // ①
strs.fold(head, |head, tail| { // ②
head.chars()
.zip(tail.chars()) // ③
.take_while(|(l, r)| l == r) // ④
.map(|(chr, _)| chr)
.collect() // ⑤
})
} else {
"".into()
}
}
上記のプログラムでは、ベクタに格納されている先頭の文字列を取得し、先頭の文字列と、ベクタに格納されている他の文字列を一文字づつ比較していると思われます。その結果をStringとしてreturnしている実装ですね。
まず①では、①の前の処理で、Vec<String>
からIter<String>
の変数を生成しているので、Iter<String>
のイテレータからnextメソッドで先頭の要素を取得し、clonedメソッドで参照のイテレータを所有権を保持するイテレータとして取得しようとしています。
ただ、next()
の結果がOption
で返ってくるので、if let
のパターンマッチで取得しようとしています。Rustはまだ慣れているとは言えないのですが、Swiftとこの辺りの雰囲気が似ていた気がしているので、あまり抵抗を感じなかったです。
Vec型の先頭から文字列の取得に成功したら②で、foldメソッドを使って、イテレータで共通のprefixを取得します。fold
メソッドでは、第一引数に比較元の先頭文字列を与え、第二引数には| head, tail | {...}
というようなクロージャを渡しています。
③のzipメソッドでは、先頭文字列と比較する文字列を引数として、charsメソッドでChars
型として渡しています。
これは以下のような形で比較が可能になると考えて間違い無いです。
#[test]
fn test_ok() {
let s1 = "abc";
let s2 = "def";
let mut iter = s1.chars().zip(s2.chars());
assert_eq!(iter.next(), Some(('a', 'd')));
}
④のtake_whileメソッドでは、クロージャで対象となるイテレータ同士を文字列の頭から比較し、一致するイテレータの文字列を取得している。(曖昧な知識なので、間違っている可能性があるので、詳細はリンク先を確認してください)
⑤collectでChars
をString
にコンバートしています。参照: How to convert iterator of chars to String?