社内で定期的にやっているRubyのメソッドを再実装する勉強法の紹介です。
※ メソッドの再実装はGrow.rbを参考にしています。
Rubyのメソッドを再実装することでRubyの理解を深め基礎力をつけることを目的にしています。
複数メンバーでやることで十人十色な実装、書き方に触れることができるので面白いです。
今回はEnumerable#chunk
を再実装します。
※問題はGitHubにまとめています。
Enumerable#chunk
要素を前から順にブロックで評価し、その結果によって要素をチャンクに分けた(グループ化した)要素を持つ Enumerator を返します。
ブロックの評価値が同じ値が続くものを一つのチャンクとして取り扱います。すなわち、ブロックの評価値が一つ前と異なる所でチャンクが区切られます。
返り値の Enumerator は各チャンクのブロック評価値と各チャンクの要素を持つ配列のペアを各要素とします。そのため、eachだと以下のようになります。
[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]]
引用:module Enumerable (Ruby 2.7.0 リファレンスマニュアル)
👻 問題
module Enumerable
def chunk
puts 'reimprement here'
end
end
ここにchunk
を再実装して正しく動くか確認します。
🤖 テストコード
require 'minitest/autorun'
require_relative 'chunk'
class ChunkTest < MiniTest::Test
# chunk {|elt| ... } -> Enumerator
def test_chunk
chunk_result = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk(&:even?)
expected_result = [[false, [3, 1]], [true, [4]], [false, [1, 5, 9]], [true, [2, 6]], [false, [5, 3, 5]]]
assert_equal expected_result, chunk_result.map { |even, ary| [even, ary] }
assert_equal Enumerator, chunk_result.class
end
end
chunk_test.rb
を chunk.rb
と同じディレクトリに置き以下を実行。
テストが通れば成功です🎉
ruby chunk_test.rb
💪 メンバー回答例
今回は以下の要件はスキップして実装していました。
ブロックの評価値が nil もしくは :_separator であった場合、 その要素を捨てます。チャンクはこの前後で区切られます。
ブロックの評価値 :_alone であった場合はその要素は 単独のチャンクをなすものと解釈されます。
※ 正解では無いのであしからず
回答例1
module Enumerable
def chunk &block
bool = nil
result = []
temp_result = []
# 1つめの真偽値を保存する
# 真偽値が切り替わるまでループする
each do |item|
temp_bool = yield item
if temp_bool != bool
bool = temp_bool
result << temp_result
temp_result = [bool, [item]]
else
temp_result[1] << item
end
end
result << temp_result
result.shift
result.each
end
end
回答例2
module Enumerable
def chunk
results = []
now_result = []
each do |item|
value = yield item
if now_result[0].nil?
# はじめて
now_result[0] = value
now_result[1] = [item]
elsif now_result[0] == value
# おなじ
now_result[1] << item
else
# ちがう
results << now_result
now_result = []
now_result[0] = value
now_result[1] = [item]
end
end
results << now_result
results.each
end
end
回答例3
module Enumerable
def chunk(&block)
result = []
tmp = []
block_result = nil
each do |n|
tmp_block_result = yield(n)
if !tmp.empty? && block_result != tmp_block_result
result << [block_result, tmp]
tmp = []
block_result = tmp_block_result
end
block_result = tmp_block_result
tmp << n
end
result << [block_result, tmp]
result.each
end
end
最後に
Rubyのメソッド再実装は
- ドキュメントを読む
- 仕様を正しく理解する
- 仕様通りに実装する
という基本的な力を養うのに良さそうです。
また、同じ課題で各自が実装すると知らなかったメソッドや書き方に出会えたり、比較して議論もしやすいです。
再実装しまくって君だけのRubyを作ろう!