:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

Enumerable モジュール

Ruby技術者認定試験の書籍写経メモ

何に使われているのか、何に使うのか

自作のクラスにeachメソッドだけ自力で実装する

めんどくさいんでクラスメソッドでいきます

class Moko
  class << self
    def each
      1.upto(5) do |index|
        yield(index)
      end
    end
  end
end

こいつのeachは1から5までを引数にブロック評価するだけのつまらないものですが、テスト用なんで許して下さい

Moko.each { |item| p item }
1
2
3
4
5
=> 1

もちろん他のメソッドは実装してないから使えません

Moko.map { |item| item }
=> NoMethodError: undefined method 'map'
Moko.methods
=> [:each, :new, :allocate, ...]
# 何か色々ありますけどね、Objectクラス周辺から継承した、オブジェクトとして生きていく為の基本的なメソッド達が

eachメソッドだけ自力で実装した自作のクラスに、Enumerableモジュールをincludeする

class Moko
  class << self
    include Enumerable

    def each
      1.upto(5) do |index|
        yield(index)
      end
    end
  end
end

完了です。メソッドを確認してみると何かいっぱいメソッドが増えてますよ! これみんな使えるようになりました

Moko.methods
:all?
:any?
:chunk
:chunk_while
:collect
:collect_concat
:count
:cycle
:detect
:drop
:drop_while
:each_cons
:each_entry
:each_slice
:each_with_index
:each_with_object
:entries
:find
:find_all
:find_index
:first
:flat_map
:grep
:grep_v
:group_by
:inject
:lazy
:map
:max
:max_by
:member?
:min
:min_by
:minmax
:minmax_by
:none?
:one?
:partition
:reduce
:reject
:reverse_each
:select
:slice_after
:slice_before
:slice_when
:sort
:sort_by
:sum
:take
:take_while
:to_a
:to_h
:uniq
:zip

Enumerable#map Enumerable#collect

メソッドの評価結果の配列を新しく生成する

Moko.map { |item| item }
=> [1, 2, 3, 4, 5]

Moko.collect { |item| item ** 2 }
=> [1, 4, 9, 16, 25]

配列もEnumerableをインクルードしているので同様な処理が可能である

[10, 20, 30, 40, 50].map { |item| item + 1 }
=> [11, 21, 31, 41, 51]

Enumerable#to_a Enumerable#entries

レシーバの各要素を集めて配列で返す

Moko.to_a
=> [1, 2, 3, 4, 5]

Moko.entries
=> [1, 2, 3, 4, 5]

ちなみに Array#to_a はselfを返す Array#entries は新たな配列を返す

a = [1, 2, 3, 4, 5]

a.object_id
=> 70287686268180

a.to_a
=> [1, 2, 3, 4, 5]

a.entries
=> [1, 2, 3, 4, 5]

a.to_a.object_id
=> 70287686268180

a.entries.object_id
=> 70287686231180

Enumerable#each_with_index

要素と0から始まるインデックスをブロックに渡して評価する

Moko.each_with_index { |item, index| p "#{item} - #{index}" }
"1 - 0"
"2 - 1"
"3 - 2"
"4 - 3"
"5 - 4"
=> Moko
[10, 20, 30, 40, 50].each_with_index { |item, index| p ({item => index}) }
{10=>0}
{20=>1}
{30=>2}
{40=>3}
{50=>4}
=> [10, 20, 30, 40, 50]

Enumerable#partition

ブロックを評価し、評価が真値の配列、偽値の配列を長さ2の配列で返す

(2..2).partition(&:odd?)
# => [[], [2]]
(2..2).partition(&:even?)
# => [[2], []]
(2..7).partition(&:odd?)
# => [[3, 5, 7], [2, 4, 6]]

Enumerable#inject Enumerable#reduce

与えた初期値と要素をブロックで連続評価して最終的な値を返す

Moko.inject(0) do |result, item|
  p "{ result: #{result}, item: #{item} }"
  result += item
end
"{ result: 0,  item: 1 }"
"{ result: 1,  item: 2 }"
"{ result: 3,  item: 3 }"
"{ result: 6,  item: 4 }"
"{ result: 10, item: 5 }"
=> 15

初期値を与えなければ、第1引数と第2引数に要素の1番目と2番目が入り、初回の評価を飛ばす

Moko.reduce do |result, item|
  p "{ result: #{result}, item: #{item} }"
  result += item
end
"{ result: 1,  item: 2 }"
"{ result: 3,  item: 3 }"
"{ result: 6,  item: 4 }"
"{ result: 10, item: 5 }"
=> 15
[10, 20, 30, 40, 50].reduce do |result, item|
  p "{ result: #{result}, item: #{item} }"
  result += item
end
"{ result: 10, item: 20 }"
"{ result: 30, item: 30 }"
"{ result: 60, item: 40 }"
"{ result: 100, item: 50 }"
=> 150

Enumerable#each_cons

要素を引数の数の長さの配列で1つずつずらしながらブロックで評価します

Moko.each_cons(3) { |items| p items }
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
=> nil
[10, 20, 30, 40, 50].each_cons(3) { |items| p items }
[10, 20, 30]
[20, 30, 40]
[30, 40, 50]
=> nil

Enumerable#each_slice

要素を引数の長さの配列に分けてブロックで評価します

Moko.each_slice(2) { |items| p items }
[1, 2]
[3, 4]
[5]
=> nil
[10, 20, 30, 40, 50].each_slice(2) { |items| p items }
[10, 20]
[30, 40]
[50]
=> nil

Enumerable#each_with_object

初期値に与えたオブジェクトに繰り返して操作を行う

[{a: 1, b: 2}, {a: 3, b: 4}, {a: 1, b: 6}].each_with_object({}) do |item, result|
  result[item[:a]] ||= []
  result[item[:a]] << item[:b]
end
=> {1=>[2, 6], 3=>[4]}
[:size, :object_id, :length].each_with_object([]) do |item, result|
  result << result.send(item)
end
=> [0, 70312923283320, 2]

Enumerable#reverse_each

要素を引数の長さの配列に分けてブロックで評価します

Moko.reverse_each { |items| p items }
5
4
3
2
1
=> Moko
[10, 20, 30, 40, 50].reverse_each { |items| p items }
50
40
30
20
10
=> [10, 20, 30, 40, 50]

Enumerable#all?

全ての要素が真値であればtrueを返す

Moko.all?
=> true
[10, 20, nil, 40, 50].all?
=> false

ブロックを渡すと評価の結果が全て真値であればtrueを返す

Moko.all? { |item| item.even? }
=> false
[10, 20, 30, 40, 50].all? { |item| item.even? }
=> true

Enumerable#any?

Moko.any?
=> true
[10, 20, nil, 40, 50].any?
=> false

ブロックを渡すと評価の結果が1つでも真値であればtrueを返す

Moko.any? { |item| item.even? }
=> true

nil.even? はエラーになるはずだが、 10.even? が真値になった時点でその後の評価は行われないためエラーにはならない

[10, nil, 30, 40, 50].any? { |item| item.even? }
=> true

Enumerable#none?

Moko.none?
=> false
[nil, false].none?
=> true

ブロックを渡すと評価の結果が1つでも真値であればfalseを返す

Moko.none? { |item| item.even? }
=> false

nil.even? はエラーになるはずだが、 19.even? が偽値になった時点でその後の評価は行われないためエラーにはならない

[10, 19, nil, 40, 50].none? { |item| item.even? }
=> false

Enumerable#one?

Moko.one?
=> false
[nil, false].one?
=> true

ブロックを渡すと評価の結果が1つだけ真値であればfalseを返す

Moko.one? { |item| item.even? }
=> false

nil.even? はエラーになるはずだが、20.even? で2つ目の真値になった時点でその後の評価は行われないためエラーにはならない

[10, 20, nil, 40, 50].one? { |item| item.even? }
=> false

Enumerable#member? Enumerable#include?

== メソッドで同値と判断された要素が存在する場合trueを返す

Moko.member?(5)
=> true
[10, 20, 30, 40].include?(50)
=> false

Enumerable#find Enumerable#detect

Moko.find do |item|
  item.even?
end
=> 2

nil.even? はエラーになるはずだが、 10.even? で真値になった時点でその後の評価は行われないためエラーにはならない

[10, nil, 30, 40].detect do |item|
  item.even?
end
=> 10

Enumerable#find_all Enumerable#select

ブロックを評価し真値と評価された要素の配列を返す

Moko.find_all do |item|
  item.even?
end
=> [2, 4]
[10, 19, 30, 40].select do |item|
  item.even?
end
=> [10, 30, 40]

Enumerable#reject

ブロックを評価し偽値と評価された要素の配列を返す

Moko.reject do |item|
  item.even?
end
=> [1, 3, 5]
[10, 19, 30, 40].reject do |item|
  item.even?
end
=> [19]

Enumerable#grep

ブロックを評価しパターンマッチする(引数.===(要素)メソッドの結果が真値)要素の配列を返す

Moko.grep(1..3)
=> [1, 2, 3]
['a', '20', '3c', '40', 50].grep(/\d/)
=> ["20", "3c", "40"]

Enumerable#sort

Moko.sort
=> [1, 2, 3, 4, 5]
[20, 40, 50, 10, 30].sort
=> [10, 20, 30, 40, 50]

ブロックを渡すとブロックが引数を2つ取り、評価の結果(1, 0, -1)にて並び替えた要素の配列を返す

Moko.sort do |a, b|
  (a % 2) <=> (b % 2)
end
=> [2, 4, 1, 3, 5]
['aa', 'aaaa', 'aaaaa', 'a', 'aaa'].sort do |a, b|
  a.length <=> b.length
end
=> ["a", "aa", "aaa", "aaaa", "aaaaa"]

Enumerable#sort_by

Moko.sort_by do |item|
  item % 2
end
=> [2, 4, 1, 3, 5]
[{id: 20}, {id: 40}, {id: 50}, {id: 10}, {id: 30}].sort_by do |item|
  item[:id]
end
=> [{:id=>10}, {:id=>20}, {:id=>30}, {:id=>40}, {:id=>50}]

Enumerable#max Enumerable#min

Moko.max
=> 5
[20, 40, 50, 10, 30].min
=> 10

ブロックを渡すとブロックが引数を2つ取り、評価の結果(1, 0, -1)にて比較した最大値、最小値の要素を返す

Moko.max do |a, b|
  (10 % a) <=> (10 % b)
end
=> 4
['aa', 'aaaa', 'aaaaa', 'a', 'aaa'].min do |a, b|
  a.length <=> b.length
end
=> "a"

Enumerable#max_by Enumerable#min_by

Moko.max_by do |item|
  10 % item
end
=> 4
[{id: 20}, {id: 40}, {id: 50}, {id: 10}, {id: 30}].min_by do |item|
  item[:id]
end
=> {:id=>10}

Enumerable#count

Moko.count
=> 5
[{id: 20}, {id: 40}, {id: 50}, {id: 10}, {id: 30}].count
=> 5

Enumerable#first Enumerable#last

Moko.first
=> 1
[{id: 20}, {id: 40}, {id: 50}, {id: 10}, {id: 30}].last(2)
=> [{:id=>10}, {:id=>30}]

[{id: 20}, {id: 40}, {id: 50}, {id: 10}, {id: 30}].last
=> {:id=>30}

Enumerable#take

Moko.take(3)
=> [1, 2, 3]
[{id: 10}, {id: 20}, {id: 30}, {id: 40}, {id: 50}].take(3)
=> [{:id=>10}, {:id=>20}, {:id=>30}]

Enumerable#drop

Moko.drop(3)
=> [4, 5]
[{id: 10}, {id: 20}, {id: 30}, {id: 40}, {id: 50}].drop(3)
=> [{:id=>40}, {:id=>50}]

Enumerable#cycle

Moko.cycle(2) { |item| p item }
1
2
3
4
5
1
2
3
4
5
=> nil
[{id: 20}, {id: 40}].cycle { |item| p item }
{:id=>20}
{:id=>40}
{:id=>20}
{:id=>40}
{:id=>20}
{:id=>40}
#............無限ループ

Enumerable#group_by

要素をブロックにて評価した結果をキーにした、同じキーを持つ要素を配列としたハッシュを返す

Moko.group_by do |item|
  5 % item
end
=> {0=>[1, 5], 1=>[2, 4], 2=>[3]}
[{id: 10}, {id: 20}, {id: 10}, {id: 20}, {id: 30}].group_by do |item|
  item[:id]
end
=> {10=>[{:id=>10}, {:id=>10}], 20=>[{:id=>20}, {:id=>20}], 30=>[{:id=>30}]}

Enumerable#zip

Moko.zip(['a', 'b', 'c', 'd', 'e'])
=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, "e"]]
[10, 20, 30, 40, 50].zip([:a, :b, :c, :d], [:A, :B, :C, :D, :E, :F])
=> [[10, :a, :A], [20, :b, :B], [30, :c, :C], [40, :d, :D], [50, nil, :E]]

Enumerable#take_while

要素をブロックにて評価した結果が偽値になった時点でループを終了し、それまでの要素の配列を返す

Moko.take_while do |item|
  item < 4
end
=> [1, 2, 3]
[1, 1, 1, 2, 2, 2].take_while do |item|
  item == 1
end
=> [1, 1, 1]

[{id: 2}, {id: 4}, {id: 6}, {id: 9}, {id: 10}].take_while do |item|
  item[:id].even?
end
=> [{:id=>2}, {:id=>4}, {:id=>6}]

Enumerable#drop_while

要素をブロックにて評価した結果が偽値になった時点でループを終了し、それ以前の要素を取り除いた配列を返す

Moko.drop_while do |item|
  item != 3
end
=> [3, 4, 5]
[1, 1, 1, 2, 2, 2].drop_while do |item|
  item == 1
end
=> [2, 2, 2]

[{id: 2}, {id: 4}, {id: 6}, {id: 9}, {id: 10}].drop_while do |item|
  item[:id].even?
end
=> [{:id=>2}, {:id=>4}, {:id=>6}]

Enumerable#lazy

a = Moko.lazy.find_all do |item|
  p 'hyouka chu'
  item.even?
end
=> #<Enumerator::Lazy: #<Enumerator::Lazy: Moko>:find_all>

a.to_a
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
=> [2, 4]

b = a.map { |item| item ** 2}
=> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: Moko>:find_all>:map>

a.to_a
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
=> [2, 4]

b.to_a
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
"hyouka chu"
=> [4, 16]