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

社内で定期的にやっているRubyのメソッドを再実装する勉強法の紹介です。
※ メソッドの再実装は[Grow.rb](https://github.com/akinov/reimplement_ruby/tree/master/enumerable)を参考にしています。

Rubyのメソッドを再実装することでRubyの理解を深め基礎力をつけることを目的にしています。
複数メンバーでやることで十人十色な実装、書き方に触れることができるので面白いです。

今回は`Enumerable#chunk`を再実装します。
※問題は[GitHub](https://github.com/akinov/reimplement_ruby)にまとめています。

# `Enumerable#chunk`

>要素を前から順にブロックで評価し、その結果によって要素をチャンクに分けた(グループ化した)要素を持つ Enumerator を返します。
ブロックの評価値が同じ値が続くものを一つのチャンクとして取り扱います。すなわち、ブロックの評価値が一つ前と異なる所でチャンクが区切られます。
返り値の Enumerator は各チャンクのブロック評価値と各チャンクの要素を持つ配列のペアを各要素とします。そのため、eachだと以下のようになります。

```ruby
[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 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/class/Enumerable.html#I_CHUNK)

# 👻 問題


```ruby:chunk.rb
module Enumerable
  def chunk
    puts 'reimprement here'
  end
end
```

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

# 🤖 テストコード

```ruby: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.rb` を `chunk.rb` と同じディレクトリに置き以下を実行。
テストが通れば成功です🎉

```bash
ruby chunk_test.rb
```

# 💪 メンバー回答例

今回は以下の要件はスキップして実装していました。
>ブロックの評価値が nil もしくは :_separator であった場合、 その要素を捨てます。チャンクはこの前後で区切られます。
ブロックの評価値 :_alone であった場合はその要素は 単独のチャンクをなすものと解釈されます。

※ 正解では無いのであしからず

## 回答例１

```ruby
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
```

## 回答例２

```ruby
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
```

## 回答例３

```ruby
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を作ろう！**