Swiftでreduceを使ってArrayからDictionaryを作る方法

More than 3 years have passed since last update.


varでfor文を回してやる方法

例えばURLのGETパラメータをDictionaryにしたいってときに、varDictionaryを宣言してfor文の中で追加してくってのが最初思いつきます。

let queries = ["id=3", "token=abc", "tag=5"]

var params = [String: String]()
for q in queries {
let v = q.componentsSeparatedByString("=")
let (key, value) = (v[0], v[1])
params[key] = value
}

println(params)
// => ["id": "3", "token": "abc", "tag": "5"]

これだとparamsに新たに値を追加できちゃうのと、せっかくSwiftなのでmapとかfilterの高階関数使ってうまいことできないかなと思ったので考えてみました。


reduceを使う

自分でmapで色々やってみましたができなくてScalaが得意な同僚に聞いたところ、そういう場合はreduceでできるということなのでやってみました。

以下のような感じです。

let queries = ["id=3", "token=abc", "tag=5"]

let params = queries.reduce([String: String]()) { (var dict, q) in
let v = q.componentsSeparatedByString("=")
let (key, value) = (v[0], v[1])
dict[key] = value
return dict
}
println(params)
// => ["id": "3", "token": "abc", "tag": "5"]

dictのところをvarで宣言してるもポイントです。最初は何もつけてなかったんですが、valueを突っ込めないなと思って試しにvarをつけたらいけました。var使っちゃってんじゃんというツッコミ入りそうですが閉じてるのでいいかなと・・


reduceではどんなことが起きてるのか

reduceの宣言を見てみます。

func reduce<U>(initial: U, combine: @noescape (U, T) -> U) -> U

今回の場合で言えば、U[String: String]のDictionary型、TString型となります。なので、当てはめてみて各ループでどのようなものが返ってきているかをまとめると、以下のようになります。各ループで返却された値が次のcombineのUになるっていうことですね。

#
combine: ([String: String],
String)
->: [String: String]

1

initialで渡した[]

"id=3"
["id": "3"]

2
["id": "3"]
"token=abc"
["id": "3", "token": "abc"]

3
["id": "3", "token": "abc"]
"tag=5"
["id": "3", "token": "abc", "tag": "5"]


まとめ

可読性を考えたらどっちがいいかは微妙なところです。ただ、reduceの動きがイマイチよくわかってなかったので理解が進んでよかったです。表にしてみたら分かった気がしました。reduceのサンプルってたいてい配列の要素を全部足し算するとかしか出てこなくてあんまり使いどころのイメージ湧かないなと思ってましたが、再帰的な処理が出てきたときはreduceを思い浮かべるといいようです。