LoginSignup
8
4

More than 3 years have passed since last update.

Rubyのメソッドを再実装する勉強法(Enumerable#chunkを再実装)

Last updated at Posted at 2020-02-05

社内で定期的にやっている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 リファレンスマニュアル)

👻 問題

chunk.rb
module Enumerable
  def chunk
    puts 'reimprement here'
  end
end

ここにchunkを再実装して正しく動くか確認します。

🤖 テストコード

chunk_test.rb
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.rbchunk.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を作ろう!

8
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4