やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

組込ライブラリ(Enumerator::Lazy)

 map や select などのメソッドの遅延評価版を提供する。

成果物

情報源

Enumerator::Lazy

map や select などのメソッドの遅延評価版を提供するためのクラス。

動作は通常の Enumerator と同じですが、以下のメソッドが遅延評価を行う (つまり、配列ではなく Enumerator を返す) ように再定義されています。

  • map/collect
  • flat_map/collect_concat
  • filter_map
  • select/find_all
  • reject
  • grep, grep_v
  • take, take_while
  • drop, drop_while
  • slice_before, slice_after, slice_when
  • chunk, chunk_while
  • uniq
  • zip (※互換性のため、ブロックを渡さないケースのみlazy)

Lazyオブジェクトは、Enumerable#lazyメソッドによって生成されます。

Lazyから値を取り出すには、Enumerator::Lazy#force または Enumerable#first を呼びます。

例:

# 二乗して偶数になるような整数を、小さい方から5個表示する
p 1.step.lazy.select{|n| (n**2).even?}.first(5)
# LTSV (http://ltsv.org/) 形式のログファイルから検索を行う
# Enumerator::Lazy#map は配列ではなく Enumerator を返すため、
# 巨大な配列を確保しようとしてメモリを使い切ったりはしない
open("log.txt"){|f|
  f.each_line.lazy.map{|line|
    Hash[line.split(/\t/).map{|s| s.split(/:/, 2)}]
  }.select{|hash|
    hash["req"] =~ /GET/ && hash["status"] == "200"
  }.each{|hash|
    p hash
  }
}

メンバ抜粋

特異メソッド

new

インスタンスメソッド

chunk chunk_while collect collect_concat drop drop_while eager enum_for filter filter_map find_all flat_map force grep grep_v lazy map reject select slice_after slice_before slice_when take take_while to_enum uniq zip

new

Enumerator::Lazy#force メソッドなどによって列挙が実行されたとき、objのeachメソッドが実行され、値が一つずつブロックに渡されます。ブロックは、yielder を使って最終的に yield される値を指定できます。

new(obj, size=nil) {|yielder, *values| ... } -> Enumerator::Lazy
module Enumerable
  def filter_map(&block)
    map(&block).compact
  end
end

class Enumerator::Lazy
  def filter_map
    Lazy.new(self) do |yielder, *values|
      result = yield *values
      yielder << result if result
    end
  end
end

1.step.lazy.filter_map{|i| i*i if i.even?}.first(5)
# => [4, 16, 36, 64, 100]

所感

 直接生成するよりもlazyメソッドによって返されるものを利用するというシーンが多そうか。むしろ基本的にlazyであり配列化したいときにto_aするのか?

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux