はじめに
Ruby は毎年バージョンアップが行われます。
パターンマッチのような大きな機能追加に注目が集まりがちですが、それ以外にもさまざまな修正・機能追加が行われています。
今回のこの記事では特に Enumerable モジュールに追加されたメソッドについて順に説明します。
Ruby 2.2 での追加機能
Enumerable#slice_after
指定した条件にマッチした要素を末尾とするチャンクに分割します。
言葉で説明するよりも例を見た方が分かりやすいと思います。
リファレンスマニュアルには下記のような例が紹介されています。
# 偶数要素をチャンクの末尾と見なす
[0,2,4,1,2,4,5,3,1,4,2].slice_after(&:even?).to_a
# => [[0], [2], [4], [1, 2], [4], [5, 3, 1, 4], [2]]
# 奇数要素をチャンクの末尾と見なす
[0,2,4,1,2,4,5,3,1,4,2].slice_after(&:odd?).to_a
# => [[0, 2, 4, 1], [2, 4, 5], [3], [1], [4, 2]]
Enumerable#slice_when
隣り合う値をブロックパラメータ elt_before、elt_after に渡し、ブロックの評価値が真になる所でチャンクを区切ります。
これも言葉で説明するよりも実際の例を見た方が分かりやすいかと思います。
# 1ずつ増加する部分配列ごとに分ける。
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.slice_when {|i, j| i+1 != j }
p b.to_a # => [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c # => [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d # => "1,2,4,9-12,15,16,19-21"
Ruby 2.3 での追加機能
Enumerable#grep_v
Enumerable#grep と逆に条件、パターンにマッチしなかった要素を返します。
パターンを引数として渡す場合に特に便利です。
これもリファレンスマニュアルの例を引用します。
(1..10).grep_v 2..5 # => [1, 6, 7, 8, 9, 10]
res = (1..10).grep_v(2..5) { |v| v * 2 }
res # => [2, 12, 14, 16, 18, 20]
ブロックを渡したときは、パターンでフィルタした上で map
と同じようにブロックの評価結果の配列を返します。
Enumerable#chunk_while
隣り合う値をブロックパラメータ elt_before、elt_after に渡し、ブロックの評価値が偽になる所でチャンクを区切ります。
Enumerable#slice_when はブロックの評価が真のところで区切りますが、このメソッドは偽のところで区切ります。
このメソッドもリファレンスマニュアルの例を引用します。
# 1ずつ増加する部分配列ごとに分ける。
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.chunk_while {|i, j| i+1 == j }
p b.to_a # => [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c # => [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d # => "1,2,4,9-12,15,16,19-21"
# 増加のみの部分配列ごとに分ける。
a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
p a.chunk_while {|i, j| i <= j }.to_a
# => [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
Ruby 2.4 で新機能
Enumerable#chunk がブロックをとらないとき Enumerator を返す
Enumerable#chunk はブロックの評価値が1つ前の要素と異なる場合、そこでチャンクが区切られます。
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk {|n|
n.even?
}.each {|even, ary|
p [even, ary]
}
# => [false, [3, 1]]
# [true, [4]]
# [false, [1, 5, 9]]
# [true, [2, 6]]
# [false, [5, 3, 5]]
このメソッドは以前から存在していましたが、さらに ブロックを取らないときに Enumerator を返すようになりました。
つまり上記と同じ内容を下記のように書けるようになりました。
e = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk
e.each do |n|
n.even?
end.each do |even, ary|
p [even, ary]
end
# => [false, [3, 1]]
# [true, [4]]
# [false, [1, 5, 9]]
# [true, [2, 6]]
# [false, [5, 3, 5]]
Enumerable#sum
要素の合計を返します。
[].sum #=> 0
[].sum(0.0) #=> 0.0
[1, 2, 3].sum #=> 6
Rails の ActiveSupport には以前からありましたが、Ruby の基本クラスに加わり、便利になりました。
Enumerable#uniq
重複した要素を取り除いた配列を返します。
ブロック引数があれば、そのブロックでの評価結果が重複していればそれを取り除きます。
olympics = {
1896 => 'Athens',
1900 => 'Paris',
1904 => 'Chicago',
1906 => 'Athens',
1908 => 'Rome',
}
olympics.uniq{|k,v| v} # => [[1896, "Athens"], [1900, "Paris"], [1904, "Chicago"], [1908, "Rome"]]
(1..100).uniq{|x| (x**2) % 10 } # => [1, 2, 3, 4, 5, 10]
Ruby 2.5
Enumerable#{any?,all?,none?,one?} がパターンを引数をとれるようになった。
grep
メソッドと同じように ブロックの代わりにオブジェクトを引数として渡して ===
メソッドの返り値を使えるようになりました。
%w[ant bear cat].all?(/t/) # => false
%w[ant bear cat].each.any?(/d/) # => false
[nil, true, 99].each.any?(Integer) # => true
%w{ant bear cat}.none?(/d/) # => true
%w{ant bear cat}.one?(/t/) # => false
Ruby 2.6
Enumerable#chain
self と引数を続けて返す Enumerator オブジェクトを返します。
e = (1..3).chain([4, 5])
e.to_a #=> [1, 2, 3, 4, 5]
(201910..201912).chain(202001..202003).to_a => [201910, 201911, 201912, 202001, 202002, 202003]
ただ、個人的には Enumerable#chain よりも同時に追加された Enumerator#+ の方が分かりやすいように感じます。好みの問題かもしれませんが。
((201910..201912).each + (202001..202003).each).to_a => [201910, 201911, 201912, 202001, 202002, 202003]
Enumerable#to_h がブロックを取れるようになった
今まで2要素配列に map してから to_h してハッシュを生成していたようなコードを中間段階を省略して、ハッシュを作れるようになりました。
# map と to_h を使った例
(1..5).map {|x| [x, x**2]}.to_h # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# reduce を使った例
(1..5).reduce({}) {|hash, x| hash.merge(x => x**2) } # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# each_with_object を使った例
(1..5).each_with_object({}) {|x, hash| hash[x] = x**2 } # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# Enumerable#to_h を使った例
(1..5).to_h {|x| [x, x ** 2]} # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
Enumerable#filter
Enumerable#filter が Enumerable#select の alias として追加されました。
Ruby 2.7
Enumerable#filter_map
今まで select と map を組み合わせたり、Enumerable#map と Array#compact を組み合わせていたコードを中間段階を省略して直接配列を作れるようになりました。
# select と map を使う例
(1..10).select{ |i| i.even? }.map{ |i| i * 2 } #=> [4, 8, 12, 16, 20]
# map と compact を使う例
(1..10).map{ |i| i * 2 if i.even? }.compact #=> [4, 8, 12, 16, 20]
# filter_map を使う例
(1..10).filter_map { |i| i * 2 if i.even? } #=> [4, 8, 12, 16, 20]
Enumerable#tally
出現回数をカウントした結果をハッシュとして返します。
["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
おわりに
Ruby がバージョンアップするたびに Enumerable モジュールに細かい修正が追加されています。
便利なメソッドを使うことで、より簡潔に効率的にコードを記述できるようになりました。
使いこなしていきましょう。