diff --git a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb index 9ad2ccd2d..7a49b10af 100644 --- a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb +++ b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb @@ -1,21 +1,34 @@ module Enumerable - # = Enumerable#lazy implementation # - # Enumerable#lazy returns an instance of Enumerator::Lazy. - # You can use it just like as normal Enumerable object, - # except these methods act as 'lazy': + # call-seq: + # enum.lazy -> lazy_enumerator + # + # Returns an Enumerator::Lazy, which redefines most Enumerable + # methods to postpone enumeration and enumerate values only on an + # as-needed basis. + # + # === Example + # + # The following program finds pythagorean triples: + # + # def pythagorean_triples + # (1..Float::INFINITY).lazy.flat_map {|z| + # (1..z).flat_map {|x| + # (x..z).select {|y| + # x*x + y*y == z*z + # }.map {|y| + # [x, y, z] + # } + # } + # } + # end + # # show first ten pythagorean triples + # p pythagorean_triples.take(10).force # take is lazy, so force is needed + # p pythagorean_triples.first(10) # first is eager + # # show pythagorean triples less than 100 + # p pythagorean_triples.take_while { |*, z| z < 100 }.force # - # - map collect - # - select find_all - # - reject - # - grep - # - grep_v - # - drop - # - drop_while - # - take_while - # - flat_map collect_concat - # - zip def lazy Enumerator::Lazy.new(self) end @@ -28,6 +41,21 @@ class Enumerator # Inspired by https://github.com/antimon2/enumerable_lz # http://jp.rubyist.net/magazine/?0034-Enumerable_lz (ja) class Lazy < Enumerator + # + # call-seq: + # Lazy.new(obj, &block) + # + # Creates a new Lazy enumerator. When the enumerator is actually enumerated + # (e.g. by calling #force), obj will be enumerated and each value passed + # to the given block. The block can yield values back by calling yielder.yield. + # For example, to create a method that acts like Array#select: + # + # def select + # Lazy.new(self) do |yielder, value| + # yielder.yield(value) if yield(value) + # end + # end + # def initialize(obj, &block) super(){|yielder| begin @@ -43,6 +71,25 @@ class Enumerator } end + # + # call-seq: + # lazy.to_enum(method = :each, *args) -> lazy_enum + # lazy.to_enum(method = :each, *args) {|*args| ... } -> lazy_enum + # lazy.enum_for(method = :each, *args) -> lazy_enum + # lazy.enum_for(method = :each, *args) {|*args| ... } -> lazy_enum + # + # Similar to Object#to_enum, except it returns a lazy enumerator. + # This makes it easy to define Enumerable methods that will + # naturally remain lazy if called on a lazy enumerator. + # + # For example: + # + # module Enumerable + # def filter_map(&block) + # map(&block).compact + # end + # end + # def to_enum(meth=:each, *args, &block) unless self.respond_to?(meth) raise ArgumentError, "undefined method #{meth}" @@ -58,6 +105,18 @@ class Enumerator end alias enum_for to_enum + # + # call-seq: + # lazy.map {|obj| block } -> lazy_enumerator + # lazy.collect {|obj| block } -> lazy_enumerator + # + # Like Enumerable#map, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.map {|i| i**2 } + # #=> #:map> + # (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3) + # #=> [1, 4, 9] + # def map(&block) Lazy.new(self){|yielder, val| yielder << block.call(val) @@ -65,6 +124,16 @@ class Enumerator end alias collect map + # + # call-seq: + # lazy.select {|obj| block } -> lazy_enumerator + # lazy.find_all {|obj| block } -> lazy_enumerator + # + # Like Enumerable#select, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.select {|i| i.even? }.first(3) + # #=> [2, 4, 6] + # def select(&block) Lazy.new(self){|yielder, val| if block.call(val) @@ -74,6 +143,15 @@ class Enumerator end alias find_all select + # + # call-seq: + # lazy.reject {|obj| block } -> lazy_enumerator + # + # Like Enumerable#reject, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.reject {|i| i.even? }.first(3) + # #=> [1, 3, 5] + # def reject(&block) Lazy.new(self){|yielder, val| unless block.call(val) @@ -82,6 +160,15 @@ class Enumerator } end + # + # call-seq: + # lazy.grep(pattern) -> lazy_enumerator + # + # Like Enumerable#grep, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.grep(1..10).force + # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # def grep(pattern) Lazy.new(self){|yielder, val| if pattern === val @@ -90,6 +177,15 @@ class Enumerator } end + # + # call-seq: + # lazy.grep_v(pattern) -> lazy_enumerator + # + # Like Enumerable#grep_v, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.grep_v(2..4).first(3) + # #=> [1, 5, 6] + # def grep_v(pattern) Lazy.new(self){|yielder, val| unless pattern === val @@ -98,6 +194,15 @@ class Enumerator } end + # + # call-seq: + # lazy.drop(n) -> lazy_enumerator + # + # Like Enumerable#drop, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.drop(3).first(3) + # #=> [4, 5, 6] + # def drop(n) dropped = 0 Lazy.new(self){|yielder, val| @@ -109,6 +214,15 @@ class Enumerator } end + # + # call-seq: + # lazy.drop_while {|obj| block } -> lazy_enumerator + # + # Like Enumerable#drop_while, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.drop_while {|i| i < 4 }.first(3) + # #=> [4, 5, 6] + # def drop_while(&block) dropping = true Lazy.new(self){|yielder, val| @@ -123,6 +237,15 @@ class Enumerator } end + # + # call-seq: + # lazy.take(n) -> lazy_enumerator + # + # Like Enumerable#take, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.take(3).force + # #=> [1, 2, 3] + # def take(n) if n == 0 return Lazy.new(self){raise StopIteration} @@ -137,6 +260,15 @@ class Enumerator } end + # + # call-seq: + # lazy.take_while {|obj| block } -> lazy_enumerator + # + # Like Enumerable#take_while, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.take_while {|i| i < 4 }.force + # #=> [1, 2, 3] + # def take_while(&block) Lazy.new(self){|yielder, val| if block.call(val) @@ -147,6 +279,16 @@ class Enumerator } end + # + # call-seq: + # lazy.flat_map {|obj| block } -> lazy_enumerator + # lazy.collect_concat {|obj| block } -> lazy_enumerator + # + # Like Enumerable#flat_map, but chains operation to be lazy-evaluated. + # + # ["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force + # #=> ["f", "o", "o", "b", "a", "r"] + # def flat_map(&block) Lazy.new(self){|yielder, val| ary = block.call(val) @@ -158,6 +300,17 @@ class Enumerator end alias collect_concat flat_map + # + # call-seq: + # lazy.zip(arg, ...) -> lazy_enumerator + # lazy.zip(arg, ...) {|arr| block } -> lazy_enumerator + # + # Like Enumerable#zip, but chains operation to be lazy-evaluated. + # However, if a block is given to zip, values are enumerated immediately. + # + # (1..Float::INFINITY).lazy.zip(('a'..'z').cycle).first(3) + # #=> [[1, "a"], [2, "b"], [3, "c"]] + # def zip(*args, &block) enums = [self] + args Lazy.new(self){|yielder, val| @@ -170,6 +323,16 @@ class Enumerator } end + # + # call-seq: + # lazy.uniq -> lazy_enumerator + # lazy.uniq {|item| block } -> lazy_enumerator + # + # Like Enumerable#uniq, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.map {|i| i % 3}.uniq.first(3) + # #=> [1, 2, 0] + # def uniq(&block) hash = {} Lazy.new(self){|yielder, val| @@ -185,6 +348,16 @@ class Enumerator } end + # + # call-seq: + # lazy.force -> array + # + # Forces lazy evaluation and returns an array containing the values + # enumerated by the lazy enumerator. This is an alias for to_a. + # + # (1..Float::INFINITY).lazy.take(3).force + # #=> [1, 2, 3] + # alias force to_a end end