第7章 コレクションを操作する: Iterator
Ruby
Published: 2019-06-25

「Rubyによるデザインパターン」の読書メモです。

時々、個人の見解入りです。

集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する

外部イテレータ

class ArrayIterator
  def initialize(array)
    @array = array
    @index = 0
  end

  def has_next?
    @index < @array.length
  end

  def item
    @array[@index]
  end

  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end

array = ['red','green', 'blue']
i = ArrayIterator.new(array)
while i.has_next?
  p "item: #{i.next_item}"
end

内部イテレータ

def for_each_element(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
end

a = [10, 20, 30]
for_each_element(a) { |element| p "The element is #{element}" }

外部イテレータと内部イテレータ

外部イテレータはクライアントが繰り返しを制御します。

内部イテレータは始めから終わりまで実行されます。

Enumerable モジュールを使う

class Account
  attr_accessor :name, :balance

  def initialize(name, balance)
    @name = name
    @balance = balance
  end

  def <=>(other)
    balance <=> other.balance
  end
end

class Portfolio
  include Enumerable

  def initialize
    @accounts = []
  end

  def each(&block)
    @accounts.each(&block)
  end

  def add_account(account)
    @accounts << account
  end
end

a1 = Account.new('account1', 200)
a2 = Account.new('account2', 400)
a3 = Account.new('account3', 600)

portfolio = Portfolio.new
arr = [a1, a2, a3]
arr.each do |a|
  portfolio.add_account(a)
end

p portfolio.any? {|account| account.balance > 500}
p portfolio.all? {|account| account.balance > 500}

コードブロックで判定条件を渡せます。

注意点

test.rb

array = ['red', 'green', 'blue', 'purple']

array.each do |color|
  p color
  if color == 'green'
    array.delete(color)
  end
end

出力結果

$ ruby test.rb
"red"
"green"
"purple"

blue がスキップされてしまいました

test.rb

def change_resistant_for_each_element(array)
  copy = Array.new(array)
  i = 0
  while i < copy.length
    yield(copy[i])
    i += 1
  end
end

array = ['red', 'green', 'blue', 'purple']
change_resistant_for_each_element(array){|color|
  p color
  if color == 'green'
    array.delete(color)
  end
}

配列を削除しても、全部捜査されました。

出力結果

$ ruby test.rb
"red"
"green"
"blue"
"purple"