mountains

共通のprefixを取得する実装を通してRustのIterator周りを眺める

とある共通の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でCharsStringにコンバートしています。参照: How to convert iterator of chars to String?

aji
Crea7
web/iOS DeveloperWhizzy Inc.

Crazy Like ODB、Play Like 勝新