外部イテレータ と 多重ループの平坦化

Author

ka

icon

kaosfield

kaosfield QR

License

CC BY-NC-SA 4.0

CC BY-NC-SA 4.0

Copyright (C) 2024 ka

About this contents

GitHub Pages: https://kaosf.github.io/20240410-ruby-enumerator

Repository: kaosf/20240410-ruby-enumerator - GitHub

GitHub Pages QR

RubyのEnumerator

e = Enumerator.new do |y|
  p y.class
  y.yield 1
end

result = e.next
#=> Enumerator::Yielder

p result
#=> 1

#next

e = Enumerator.new do |y|
  y.yield 1
  y.yield 2
  y.yield 3
end

p e.next
#=> 1
p e.next
#=> 2
p e.next
#=> 3

#each

e = Enumerator.new do |y|
  y.yield 1
  y.yield 2
  y.yield 3
end

e.each do
  p _1
end
#=> 1
#=> 2
#=> 3

Array#each

each メソッドは Enumerator クラスのインスタンスを返す

e = [1, 2, 3].each
p e.class
#=> Enumerator

p e.next
#=> 1
p e.next
#=> 2
p e.next
#=> 3

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

内部イテレータ

制御の面倒を見て貰う

[1, 2, 3].each { p _1 }

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

外部イテレータ

自分で制御の面倒を見る

e = [1, 2, 3].each

loop do
  p e.next
rescue
  break
end

多重ループの平坦化

xs = [1000, 2000]
ys = [100, 200]
zs = [10, 20]
ws = [1, 2]

xs.each do |x|
  ys.each do |y|
    zs.each do |z|
      ws.each do |w|
        p x + y + z + w
      end
    end
  end
end

xs = [1000, 2000]
ys = [100, 200]
zs = [10, 20]
ws = [1, 2]

xs_enumerator = Enumerator.new do |yielder|
  xs.each do |x|
    yielder.yield x
  end
end
xys_enumerator = Enumerator.new do |yielder|
  xs_enumerator.each do |x|
    ys.each do |y|
      yielder.yield x, y
    end
  end
end
xyzs_enumerator = Enumerator.new do |yielder|
  xys_enumerator.each do |x, y|
    zs.each do |z|
      yielder.yield x, y, z
    end
  end
end
xyzws_enumerator = Enumerator.new do |yielder|
  xyzs_enumerator.each do |x, y, z|
    ws.each do |w|
      yielder.yield x, y, z, w
    end
  end
end

xyzws_enumerator.each do |x, y, z, w|
  p x + y + z + w
end

注釈

結局ネストが深いままでは?

ネストが深くなったままなのが問題ではない

制御を自分で面倒見られるようになることでループがその外側と内側のループから独立することが出来た

どの階層のループもそれぞれ「外側から何を受け取るか」「内側に何を渡すか」だけに集中することが出来る構造になっている

例えば z のループの直前で x と y を用いた前処理を実施する必要が出た場合でも

xys_enumerator = Enumerator.new do |yielder|
  xs_enumerator.each do |x|
    ys.each do |y|
      do_something_with x, y
      yielder.yield x, y
    end
  end
end

あるいは

xyzs_enumerator = Enumerator.new do |yielder|
  xys_enumerator.each do |x, y|
    do_something_with x, y
    zs.each do |z|
      yielder.yield x, y, z
    end
  end
end

その他

xs = [1000, 2000]
ys = [100, 200]
zs = [10, 20]
ws = [1, 2]

xyzws_enumerator = Enumerator.new do |yielder|
  xs.each do |x|
    ys.each do |y|
      zs.each do |z|
        ws.each do |w|
          yielder.yield x, y, z, w
        end
      end
    end
  end
end

xyzws_enumerator.each do |x, y, z, w|
  p x + y + z + w
end

デメリット

レキシカルスコープが分散されてしまいクロージャの恩恵が得られないため必要な値はすべて明示的に yielder を介してやりとりしなければならない