Created
December 4, 2012 06:55
-
-
Save yuki-teraoka/4201351 to your computer and use it in GitHub Desktop.
Enumerator::Lazy for ruby 1.9
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Enumerable | |
def lazy | |
enum = self | |
Enumerator::Lazy.new(self).tap do |obj| | |
obj.instance_variable_set(:@enum , enum) | |
obj.instance_variable_set(:@method, false) | |
obj.instance_variable_set(:@args , []) | |
end | |
end | |
end | |
class Enumerator::Lazy < Enumerator | |
class << self | |
def _create(enum, method = :each, *args, &block) | |
Enumerator::Lazy.new(&block).tap do |obj| | |
obj.instance_variable_set(:@enum , enum) | |
obj.instance_variable_set(:@method, method) | |
obj.instance_variable_set(:@args , args) | |
end | |
end | |
end | |
def inspect | |
if instance_variable_defined?(:@enum) | |
result = "\#<#{self.class.name}: #{@enum.inspect}" | |
result << ":#{@method}" if @method | |
result << '(' << @args.map(&:inspect).join(',') << ')' unless @args.empty? | |
result << '>' | |
return result | |
else | |
return super | |
end | |
end | |
def map(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy map without a block" unless block | |
Enumerator::Lazy._create(self, :map) do |yielder| | |
enum.each {|v| yielder << block.call(v)} | |
end | |
end | |
def collect(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy collect without a block" unless block | |
Enumerator::Lazy._create(self, :collect) do |yielder| | |
enum.each {|v| yielder << block.call(v)} | |
end | |
end | |
def flat_map(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy flat_map without a block" unless block | |
Enumerator::Lazy._create(self, :flat_map) do |yielder| | |
enum.each do |v| | |
ary = block.call(v) | |
unless ary.kind_of?(Enumerable) | |
if ary.respond_to?(:to_ary) | |
ary = ary.to_ary | |
else | |
ary = [ary] | |
end | |
end | |
ary.each {|av| yielder << av} | |
end | |
end | |
end | |
def collect_concat(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy collect_concat without a block" unless block | |
Enumerator::Lazy._create(self, :collect_concat) do |yielder| | |
enum.each do |v| | |
ary = block.call(v) | |
unless ary.kind_of?(Enumerable) | |
if ary.respond_to?(:to_ary) | |
ary = ary.to_ary | |
else | |
ary = [ary] | |
end | |
end | |
ary.each {|av| yielder << av} | |
end | |
end | |
end | |
def select(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy select without a block" unless block | |
Enumerator::Lazy._create(self, :select) do |yielder| | |
_enum = enum.to_enum | |
loop do | |
begin | |
value = _enum.next | |
yielder << value if block.call(value) | |
rescue StopIteration | |
break | |
end | |
end | |
end | |
end | |
def find_all(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy find_all without a block" unless block | |
Enumerator::Lazy._create(self, :find_all) do |yielder| | |
_enum = enum.to_enum | |
loop do | |
begin | |
value = _enum.next | |
yielder << value if block.call(value) | |
rescue StopIteration | |
break | |
end | |
end | |
end | |
end | |
def reject(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy reject without a block" unless block | |
Enumerator::Lazy._create(self, :reject) do |yielder| | |
_enum = enum.to_enum | |
loop do | |
begin | |
value = _enum.next | |
yielder << value unless block.call(value) | |
rescue StopIteration | |
break | |
end | |
end | |
end | |
end | |
def grep(pattern) | |
enum = self | |
block = block_given? ? Proc.new : nil | |
Enumerator::Lazy._create(self, :grep, pattern) do |yielder| | |
_enum = enum.to_enum | |
loop do | |
begin | |
value = _enum.next | |
if pattern === value | |
yielder << (block ? block.call(value) : value) | |
end | |
rescue StopIteration | |
break | |
end | |
end | |
end | |
end | |
def zip(*lists) | |
return super if block_given? | |
enum = self | |
enum_lists = lists.map{|list| Enumerator.new(list)} | |
Enumerator::Lazy._create(self, :zip, *lists) do |yielder| | |
enum.each do |v| | |
yielder << enum_lists.map do |e| | |
begin | |
e.next | |
rescue StopIteration | |
nil | |
end | |
end.unshift(v) | |
end | |
end | |
end | |
def take(n) | |
enum = self | |
Enumerator::Lazy._create(self, :take, n) do |yielder| | |
i = 0 | |
next if i >= n | |
enum.each do |v| | |
yielder << v | |
break if (i+=1) >= n | |
end | |
end | |
end | |
def take_while(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy take_while without a block" unless block | |
Enumerator::Lazy._create(self, :take_while) do |yielder| | |
enum.each do |v| | |
break unless block.call(v) | |
yielder << v | |
end | |
end | |
end | |
def drop(n) | |
enum = self | |
Enumerator::Lazy._create(self, :drop, n) do |yielder| | |
i = 0 | |
enum.each do |v| | |
yielder << v unless (i+=1) <= n | |
end | |
end | |
end | |
def drop_while(&block) | |
enum = self | |
raise ArgumentError, "tried to call lazy drop_while without a block" unless block | |
Enumerator::Lazy._create(self, :drop_while) do |yielder| | |
droped = true | |
enum.each do |v| | |
yielder << v unless (droped and droped = block.call(v)) | |
end | |
end | |
end | |
def cycle(n = nil) | |
return super if block_given? | |
enum = self | |
if n | |
return Enumerator::Lazy._create(self, :cycle, n) do |yielder| | |
n.times {enum.each {|v| yielder << v}} | |
end | |
else | |
return Enumerator::Lazy._create(self, :cycle) do |yielder| | |
loop {enum.each {|v| yielder << v}} | |
end | |
end | |
end | |
def lazy | |
self | |
end | |
def force | |
to_a | |
end | |
end | |
# --- test code ----------------------------------------------------------------- | |
if __FILE__ == $0 | |
require 'test/unit' | |
class TestLazyEnumerator < Test::Unit::TestCase | |
class Step | |
include Enumerable | |
attr_reader :current, :args | |
def initialize(enum) | |
@enum = enum | |
@current = nil | |
@args = nil | |
end | |
def each(*args) | |
@args = args | |
@enum.each {|i| @current = i; yield i} | |
end | |
end | |
def test_initialize | |
assert_equal([1, 2, 3], [1, 2, 3].lazy.to_a) | |
assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]).to_a) | |
end | |
# def test_each_args | |
# a = Step.new(1..3) | |
# assert_equal(1, a.lazy.each(4).first) | |
# assert_equal([4], a.args) | |
# end | |
# def test_each_line | |
# name = lineno = nil | |
# File.open(__FILE__) do |f| | |
# f.each("").map do |paragraph| | |
# paragraph[/\A\s*(.*)/, 1] | |
# end.find do |line| | |
# if name = line[/^class\s+(\S+)/, 1] | |
# lineno = f.lineno | |
# true | |
# end | |
# end | |
# end | |
# assert_equal(self.class.name, name) | |
# assert_operator(lineno, :>, 2) | |
# | |
# name = lineno = nil | |
# File.open(__FILE__) do |f| | |
# f.lazy.each("").map do |paragraph| | |
# paragraph[/\A\s*(.*)/, 1] | |
# end.find do |line| | |
# if name = line[/^class\s+(\S+)/, 1] | |
# lineno = f.lineno | |
# true | |
# end | |
# end | |
# end | |
# assert_equal(self.class.name, name) | |
# assert_equal(2, lineno) | |
# end | |
def test_select | |
a = Step.new(1..6) | |
assert_equal(4, a.select {|x| x > 3}.first) | |
assert_equal(6, a.current) | |
assert_equal(4, a.lazy.select {|x| x > 3}.first) | |
assert_equal(4, a.current) | |
a = Step.new(['word', nil, 1]) | |
assert_raise(TypeError) {a.select {|x| "x"+x}.first} | |
assert_equal(nil, a.current) | |
assert_equal("word", a.lazy.select {|x| "x"+x}.first) | |
assert_equal("word", a.current) | |
end | |
def test_select_multiple_values | |
e = Enumerator.new { |yielder| | |
for i in 1..5 | |
yielder.yield(i, i.to_s) | |
end | |
} | |
assert_equal([[2, "2"], [4, "4"]], | |
e.select {|x| x[0] % 2 == 0}) | |
assert_equal([[2, "2"], [4, "4"]], | |
e.lazy.select {|x| x[0] % 2 == 0}.force) | |
end | |
def test_map | |
a = Step.new(1..3) | |
assert_equal(2, a.map {|x| x * 2}.first) | |
assert_equal(3, a.current) | |
assert_equal(2, a.lazy.map {|x| x * 2}.first) | |
assert_equal(1, a.current) | |
end | |
def test_flat_map | |
a = Step.new(1..3) | |
assert_equal(2, a.flat_map {|x| [x * 2]}.first) | |
assert_equal(3, a.current) | |
assert_equal(2, a.lazy.flat_map {|x| [x * 2]}.first) | |
assert_equal(1, a.current) | |
end | |
def test_flat_map_nested | |
a = Step.new(1..3) | |
assert_equal([1, "a"], | |
a.flat_map {|x| ("a".."c").map {|y| [x, y]}}.first) | |
assert_equal(3, a.current) | |
assert_equal([1, "a"], | |
a.lazy.flat_map {|x| ("a".."c").lazy.map {|y| [x, y]}}.first) | |
assert_equal(1, a.current) | |
end | |
def test_flat_map_to_ary | |
to_ary = Class.new { | |
def initialize(value) | |
@value = value | |
end | |
def to_ary | |
[:to_ary, @value] | |
end | |
} | |
assert_equal([:to_ary, 1, :to_ary, 2, :to_ary, 3], | |
[1, 2, 3].flat_map {|x| to_ary.new(x)}) | |
assert_equal([:to_ary, 1, :to_ary, 2, :to_ary, 3], | |
[1, 2, 3].lazy.flat_map {|x| to_ary.new(x)}.force) | |
end | |
def test_flat_map_non_array | |
assert_equal(["1", "2", "3"], [1, 2, 3].flat_map {|x| x.to_s}) | |
assert_equal(["1", "2", "3"], [1, 2, 3].lazy.flat_map {|x| x.to_s}.force) | |
end | |
def test_reject | |
a = Step.new(1..6) | |
assert_equal(4, a.reject {|x| x < 4}.first) | |
assert_equal(6, a.current) | |
assert_equal(4, a.lazy.reject {|x| x < 4}.first) | |
assert_equal(4, a.current) | |
a = Step.new(['word', nil, 1]) | |
assert_equal(nil, a.reject {|x| x}.first) | |
assert_equal(1, a.current) | |
assert_equal(nil, a.lazy.reject {|x| x}.first) | |
assert_equal(nil, a.current) | |
end | |
def test_reject_multiple_values | |
e = Enumerator.new { |yielder| | |
for i in 1..5 | |
yielder.yield(i, i.to_s) | |
end | |
} | |
assert_equal([[2, "2"], [4, "4"]], | |
e.reject {|x| x[0] % 2 != 0}) | |
assert_equal([[2, "2"], [4, "4"]], | |
e.lazy.reject {|x| x[0] % 2 != 0}.force) | |
end | |
def test_grep | |
a = Step.new('a'..'f') | |
assert_equal('c', a.grep(/c/).first) | |
assert_equal('f', a.current) | |
assert_equal('c', a.lazy.grep(/c/).first) | |
assert_equal('c', a.current) | |
assert_equal(%w[a e], a.grep(proc {|x| /[aeiou]/ =~ x})) | |
assert_equal(%w[a e], a.lazy.grep(proc {|x| /[aeiou]/ =~ x}).to_a) | |
end | |
def test_grep_with_block | |
a = Step.new('a'..'f') | |
assert_equal('C', a.grep(/c/) {|i| i.upcase}.first) | |
assert_equal('C', a.lazy.grep(/c/) {|i| i.upcase}.first) | |
end | |
def test_grep_multiple_values | |
e = Enumerator.new { |yielder| | |
3.times { |i| | |
yielder.yield(i, i.to_s) | |
} | |
} | |
assert_equal([[2, "2"]], e.grep(proc {|x| x == [2, "2"]})) | |
assert_equal([[2, "2"]], e.lazy.grep(proc {|x| x == [2, "2"]}).force) | |
assert_equal(["22"], | |
e.lazy.grep(proc {|x| x == [2, "2"]}, &:join).force) | |
end | |
def test_zip | |
a = Step.new(1..3) | |
assert_equal([1, "a"], a.zip("a".."c").first) | |
assert_equal(3, a.current) | |
assert_equal([1, "a"], a.lazy.zip("a".."c").first) | |
assert_equal(1, a.current) | |
end | |
def test_zip_short_arg | |
a = Step.new(1..5) | |
assert_equal([5, nil], a.zip("a".."c").last) | |
assert_equal([5, nil], a.lazy.zip("a".."c").force.last) | |
end | |
def test_zip_without_arg | |
a = Step.new(1..3) | |
assert_equal([1], a.zip.first) | |
assert_equal(3, a.current) | |
assert_equal([1], a.lazy.zip.first) | |
assert_equal(1, a.current) | |
end | |
def test_zip_with_block | |
# zip should be eager when a block is given | |
a = Step.new(1..3) | |
ary = [] | |
assert_equal(nil, a.lazy.zip("a".."c") {|x, y| ary << [x, y]}) | |
assert_equal(a.zip("a".."c"), ary) | |
assert_equal(3, a.current) | |
end | |
def test_take | |
a = Step.new(1..10) | |
assert_equal(1, a.take(5).first) | |
assert_equal(5, a.current) | |
assert_equal(1, a.lazy.take(5).first) | |
assert_equal(1, a.current) | |
assert_equal((1..5).to_a, a.lazy.take(5).force) | |
assert_equal(5, a.current) | |
a = Step.new(1..10) | |
assert_equal([], a.lazy.take(0).force) | |
assert_equal(nil, a.current) | |
end | |
def test_take_recycle | |
bug6428 = '[ruby-dev:45634]' | |
a = Step.new(1..10) | |
take5 = a.lazy.take(5) | |
assert_equal((1..5).to_a, take5.force, bug6428) | |
assert_equal((1..5).to_a, take5.force, bug6428) | |
end | |
def test_take_while | |
a = Step.new(1..10) | |
assert_equal(1, a.take_while {|i| i < 5}.first) | |
assert_equal(5, a.current) | |
assert_equal(1, a.lazy.take_while {|i| i < 5}.first) | |
assert_equal(1, a.current) | |
assert_equal((1..4).to_a, a.lazy.take_while {|i| i < 5}.to_a) | |
end | |
def test_drop | |
a = Step.new(1..10) | |
assert_equal(6, a.drop(5).first) | |
assert_equal(10, a.current) | |
assert_equal(6, a.lazy.drop(5).first) | |
assert_equal(6, a.current) | |
assert_equal((6..10).to_a, a.lazy.drop(5).to_a) | |
end | |
def test_drop_while | |
a = Step.new(1..10) | |
assert_equal(5, a.drop_while {|i| i < 5}.first) | |
assert_equal(10, a.current) | |
assert_equal(5, a.lazy.drop_while {|i| i < 5}.first) | |
assert_equal(5, a.current) | |
assert_equal((5..10).to_a, a.lazy.drop_while {|i| i < 5}.to_a) | |
end | |
def test_drop_and_take | |
assert_equal([4, 5], (1..Float::INFINITY).lazy.drop(3).take(2).to_a) | |
end | |
def test_cycle | |
a = Step.new(1..3) | |
assert_equal("1", a.cycle(2).map(&:to_s).first) | |
assert_equal(3, a.current) | |
assert_equal("1", a.lazy.cycle(2).map(&:to_s).first) | |
assert_equal(1, a.current) | |
end | |
def test_cycle_with_block | |
# cycle should be eager when a block is given | |
a = Step.new(1..3) | |
ary = [] | |
assert_equal(nil, a.lazy.cycle(2) {|i| ary << i}) | |
assert_equal(a.cycle(2).to_a, ary) | |
assert_equal(3, a.current) | |
end | |
def test_cycle_chain | |
a = 1..3 | |
assert_equal([1,2,3,1,2,3,1,2,3,1], a.lazy.cycle.take(10).force) | |
assert_equal([2,2,2,2,2,2,2,2,2,2], a.lazy.cycle.select {|x| x == 2}.take(10).force) | |
assert_equal([2,2,2,2,2,2,2,2,2,2], a.lazy.select {|x| x == 2}.cycle.take(10).force) | |
end | |
def test_force | |
assert_equal([1, 2, 3], (1..Float::INFINITY).lazy.take(3).force) | |
end | |
def test_inspect | |
assert_equal("#<Enumerator::Lazy: 1..10:each>", | |
Enumerator::Lazy.new(1..10).inspect) | |
assert_equal("#<Enumerator::Lazy: 1..10:cycle(2)>", | |
Enumerator::Lazy.new(1..10, :cycle, 2).inspect) | |
assert_equal("#<Enumerator::Lazy: 1..10>", (1..10).lazy.inspect) | |
assert_equal('#<Enumerator::Lazy: #<Enumerator: "foo":each_char>>', | |
"foo".each_char.lazy.inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>", | |
(1..10).lazy.map {}.inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:take(0)>", | |
(1..10).lazy.take(0).inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:take(3)>", | |
(1..10).lazy.take(3).inspect) | |
assert_equal('#<Enumerator::Lazy: #<Enumerator::Lazy: "a".."c">:grep(/b/)>', | |
("a".."c").lazy.grep(/b/).inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle(3)>", | |
(1..10).lazy.cycle(3).inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle>", | |
(1..10).lazy.cycle.inspect) | |
assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle(3)>", | |
(1..10).lazy.cycle(3).inspect) | |
l = (1..10).lazy.map {}.collect {}.flat_map {}.collect_concat {}.select {}.find_all {}.reject {}.grep(1).zip(?a..?c).take(10).take_while {}.drop(3).drop_while {}.cycle(3) | |
assert_equal(<<EOS.chomp, l.inspect) | |
#<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>:collect>:flat_map>:collect_concat>:select>:find_all>:reject>:grep(1)>:zip("a".."c")>:take(10)>:take_while>:drop(3)>:drop_while>:cycle(3)> | |
EOS | |
end | |
# def test_size | |
# lazy = [1, 2, 3].lazy | |
# assert_equal 3, lazy.size | |
# assert_equal 42, Enumerator.new(42){}.lazy.size | |
# %w[map collect flat_map collect_concat].map(&:to_sym).each do |m| | |
# assert_equal 3, lazy.send(m){}.size | |
# end | |
# assert_equal 3, lazy.zip([4]).size | |
# %w[select find_all reject take_while drop_while].map(&:to_sym).each do |m| | |
# assert_equal nil, lazy.send(m){}.size | |
# end | |
# assert_equal nil, lazy.grep(//).size | |
# | |
# assert_equal 2, lazy.take(2).size | |
# assert_equal 3, lazy.take(4).size | |
# assert_equal 4, loop.lazy.take(4).size | |
# assert_equal nil, lazy.select{}.take(4).size | |
# | |
# assert_equal 1, lazy.drop(2).size | |
# assert_equal 0, lazy.drop(4).size | |
# assert_equal Float::INFINITY, loop.lazy.drop(4).size | |
# assert_equal nil, lazy.select{}.drop(4).size | |
# | |
# assert_equal 0, lazy.cycle(0).size | |
# assert_equal 6, lazy.cycle(2).size | |
# assert_equal 3 << 80, 4.times.inject(lazy){|enum| enum.cycle(1 << 20)}.size | |
# assert_equal Float::INFINITY, lazy.cycle.size | |
# assert_equal Float::INFINITY, loop.lazy.cycle(4).size | |
# assert_equal Float::INFINITY, loop.lazy.cycle.size | |
# assert_equal nil, lazy.select{}.cycle(4).size | |
# assert_equal nil, lazy.select{}.cycle.size | |
# end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment