Just do IT

思うは招く

Ruby Hash#slice の便利な使い方

たとえば、こんな配列とHashがあったとする。

keys = %w[hoge bar]

hash = { 'hoge'=> 'ほげ', 'aaa'=> 'あああ', 'bar'=> 'ばあー'}

keys配列の各要素をkeyとして、hashからそれぞれ対応するキーとバリューを取得したいときはHash#sliceを使うと便利。

> hash.slice(*keys)
=> {"hoge"=>"ほげ", "bar"=>"ばあー"}

*keysとしているところに注意。

Hash#slice (Ruby 3.1 リファレンスマニュアル)

Ruby の select と find の違い

たとえば以下の配列から、初めに見つかった3の倍数を取り出したいとき。

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]

selectを使うとこうなる。

> nums.select { |num| (num % 3).zero? }.first
=> 3

これでも期待するデータを取得できるが、配列内のすべての要素をチェックしているため効率が悪い。最後のfirstがそれを物語っている。firstを取り除いてみると、

> nums.select{|num| (num % 3).zero? }
=> [3, 6, 9]

となり、やはりすべての要素をチェックして無駄な配列を作っている。

こういった場合はfindのほうが効率的だ。

> nums.find { |num| (num % 3).zero? }
=> 3

最初に3の倍数を見つけて、そこで探索を終了する。

Ruby の flat_map は flatten.map と同じ挙動にはならない

flat_mapflatten.mapを省略したものだと勘違いしていた話。

ここにネストした配列がある。

> arr = [[1,2], [3,4]]
=> [[1, 2], [3, 4]]

flattenすると、平坦な配列になる。

> arr.flatten
=> [1, 2, 3, 4]

flattenした後にmapして各要素を2倍にしたいときはこうなる。

> arr.flatten.map { |num| num * 2 }
=> [2, 4, 6, 8]

では、flatten.mapflat_mapに置き換えるとどうなるか?

> arr.flat_map { |num| num * 2 }
=> [1, 2, 1, 2, 3, 4, 3, 4]

flatten.mapと同じ結果にはなっていない。

> arr.flatten.map { |num| num * 2 }
=> [2, 4, 6, 8]

> arr.flat_map { |num| num * 2 }
=> [1, 2, 1, 2, 3, 4, 3, 4]

flat_mapのとき、numには何が渡ってくるのか?

> arr.flat_map { |num| p num }
[1, 2]
[3, 4]

ネストした配列を取得していることがわかった。arr.flat_map { |num| num * 2 }の挙動としては、

> [1, 2] * 2
=> [1, 2, 1, 2]

> [3, 4] * 2
=> [3, 4, 3, 4]

をしてから、返り値を連結した配列を返す。

> arr.flat_map { |num| num * 2 }
=> [1, 2, 1, 2, 3, 4, 3, 4]

よって、さきほどのflatten.mapと同じ結果にしたいならこう書く必要がある。

> arr.flat_map { |arr| arr.map { |num| num * 2}}
=> [2, 4, 6, 8]

> arr.flatten.map { |num| num * 2 }
=> [2, 4, 6, 8]

まとめると、Rubyflat_mapflatten.mapと同じ挙動にはならない。ちなみにflat_mapエイリアスメソッドとしてcollect_concatが存在する。

参考