【Ruby】Arrayクラスのインスタンスメソッド
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
個人的にRubyのメソッドを学習したくて、現在「Rubyがミニツク」などでメソッドの使い方を学習しています。やはりあまりにも数が多く、「どうせググるし覚えなくてもいいんじゃね」という甘えた考えが出てきてしまいます。ですが、頭の片隅にでも各メソッドの動きをちょこっと覚えておくだけでも、もっと簡単にコードをかけたりするものだと思うので、今回はArrayクラスのRubyのメソッドについて学習します。
全部はアウトプットできない
とはいうもののArrayクラスのメソッドは大量です。(どのクラスもそうですが)
ですので、比較的に利用頻度の高いメソッドを紹介します。伊藤淳一さんが利用頻度の高いメソッドを下記記事で提示していますので、そこから個人的に使ったことがない物を紹介します。
それだけだと伊藤さんの記事を見ればいいじゃんとなるので、Arrayクラスのインスタンスメソッドからもいくつか紹介します。下記の合計8個のメソッドを取り上げます。
- flatten/flatten!/flat_map/collect_concat
- transpose
- zip
- uniq/uniq!
- each_with_index
- difference
- filter_map
flatten/flatten!/flat_map/collect_concat
flatten は入れ子構造になっている配列を平坦化した配列を生成して返します。平坦化とはつまり入れ子構造になっていない状態です。
a = [1, [2, 3, [4], 5]] p a.flatten #=> [1, 2, 3, 4, 5]
変数aは3重の入れ子構造になっていますが、flattenを使うことで入れ子構造になっている箇所が全て平坦化されます。flattenは「平らにする」という意味で、名前と処理が似ているので覚えやすいです。
前提としてレシーバーは入れ子構造の配列でないと変化がありません。入れ子構造になっていない配列をレシーバーにすると、そのまま平坦な配列が返却されます。
a = [1, [2, 3, [4], 5]] p a.flatten #=> [1, 2, 3, 4, 5] p a.flatten.flatten #=> [1, 2, 3, 4, 5]
flattenは元の配列を破壊しませんが、flatten!は元の配列を破壊します。
a = [1, [2, 3, [4], 5]] p flatten #=> [1, 2, 3, 4, 5] p a #=> [1, [2, 3, [4], 5]] p flatten! #=> [1, 2, 3, 4, 5] p a #=> [1, 2, 3, 4, 5]
flat_mapはmapメソッドとflattenメソッドの組み合わせたような処理をします。collect_concatはflat_mapのエイリアスです。
mapメソッドだけだと下記のような入れ子構造の配列に対して全ての要素を2倍にした場合、返却される値も入れ子構造になってしまいます。
[[1,2], [3,4]].map{|array| array.map{|j| j*2}} # => [[2,4], [6,8]]
flat_mapを使うことで返却値を平坦な配列にすることができます。
[[1,2], [3,4]].flat_map{|array| array.map{|j| j*2}} # => [2,4,6,8]
transpose
transposeメソッドはレシーバーの配列を行列と見立てて、行列の入れ替えをするメソッドです。
p [[1,2], [3,4], [5,6]].transpose # => [[1, 3, 5], [2, 4, 6]]
行列の入れ替えのためにレシーバーの配列は下記要件を満たしておく必要があります。
まずレシーバーは入れ子構造になっていないといけません。行列と見立てることができないためです。よって平坦な一次元配列をレシーバーとするとエラーが起きます。
[1,2,3].transpose # => TypeError (no implicit conversion of Integer into Array)
[[1,2], [3,4], [5,6,7]].transpose # => IndexError (element size differs (3 should be 2))
しかし、空の配列に他してはレシーバーにすることができます。
[].transpose
# => []
zip
引数に渡した配列の各要素からなる配列とレシーバーの配列を組み合わせた入れ子構造に配列を返却します。入れ子の数はレシーバーの要素数に準拠します。引数の要素数がレシーバーの要素数より少ない場合、入れ子の中の該当箇所の要素はnilになる。
a1 = [1,2,3] => [1, 2, 3] a2 = [4,5] => [4, 5] a3 = [6,7,8,9] => [6, 7, 8, 9] a1.zip(a2,a3) => [[1, 4, 6], [2, 5, 7], [3, nil, 8]] # レシーバー(a1)の要素数は3なのでa3の4番目の要素は消える a1 = [1,2,3,4] => [1,2,3,4] a1.zip(a2,a3) => [[1, 4, 6], [2, 5, 7], [3, nil, 8], [4, nil, 9]] # レシーバー(a1)の要素数は4なので4つの配列が返却される
uniq/uniq!
配列から重複した要素を取り除いた新しい配列を返します。この処理はかなり使い勝手が良さそうですね!
[1,2,3,4,5,6,1,2].uniq # => [1,2,3,4,5,6]
uniqについて面白いと思ったのはブロックを与えると挙動が変化することです。例えば下記のような配列があるとします。
[1,2,3,4,5,6,"1","2"]
これにuniqメソッドを使用しても返却される配列に変化はありません。1はintegerですが"1"はstringだからです。
[1,2,3,4,5,6,"1","2"].uniq #=> [1,2,3,4,5,6,"1","2"]
しかし、uniqメソッドにブロックを以下のように渡すことで、ブロックが返した値が重複した要素を取り除いた配列を返します
[1,2,3,4,5,6,"1","2"].uniq { |n| n.to_s } #=> [1,2,3,4,5,6]
評価の順番は先頭の要素からです。つまり、もしstringの"1"と"2"がintegerの1,2より先頭に近い要素にあれば、取り除かれるのはintegerの1と2になります。
["1","2",1,2,3,4,5,6].uniq { |n| n.to_s } #=> ["1","2",3,4,5,6]
each_with_index
要素とそのインデックスをブロックに渡して繰り返し処理を実行します。
ary=["a", "b", "c"] ary.each_with_index{|v,idx| p v p idx } # => "a", 0, "b", 1, "c", 2
eachで繰り返し処理を行うよりもこちらの方がより複雑な処理をすることができそうです。
difference
引数の配列の要素を取り除いた配列を生成して返します。
[ 1, 1, 2, 2, 3, 3, 4, 5 ].difference([ 1, 2, 4 ]) # => [ 3, 3, 5 ]
挙動としてはかなりシンプルです。引数は必ず配列ではないといけないため、注意してください。
[ 1, 1, 2, 2, 3, 3, 4, 5 ].difference(1,2,3) # => TypeError (no implicit conversion of Integer into Array)