Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

419 lines (339 sloc) 8.294 kb
#!/usr/bin/env ruby
#--
# $Idaemons: /home/cvs/rb/generator.rb,v 1.8 2001/10/03 08:54:32 knu Exp $
# $RoughId: generator.rb,v 1.10 2003/10/14 19:36:58 knu Exp $
# $Id: generator.rb 15954 2008-04-10 10:52:50Z knu $
#++
#
# = generator.rb: convert an internal iterator to an external one
#
# Copyright (c) 2001,2003 Akinori MUSHA <knu@iDaemons.org>
#
# All rights reserved. You can redistribute and/or modify it under
# the same terms as Ruby.
#
# == Overview
#
# This library provides the Generator class, which converts an
# internal iterator (i.e. an Enumerable object) to an external
# iterator. In that form, you can roll many iterators independently.
#
# The SyncEnumerator class, which is implemented using Generator,
# makes it easy to roll many Enumerable objects synchronously.
#
# See the respective classes for examples of usage.
#
# Generator converts an internal iterator (i.e. an Enumerable object)
# to an external iterator.
#
# Note that it is not very fast since it is implemented using
# continuations, which are currently slow.
#
# == Example
#
# require 'generator'
#
# # Generator from an Enumerable object
# g = Generator.new(['A', 'B', 'C', 'Z'])
#
# while g.next?
# puts g.next
# end
#
# # Generator from a block
# g = Generator.new { |g|
# for i in 'A'..'C'
# g.yield i
# end
#
# g.yield 'Z'
# }
#
# # The same result as above
# while g.next?
# puts g.next
# end
#
class Generator
include Enumerable
# Creates a new generator either from an Enumerable object or from a
# block.
#
# In the former, block is ignored even if given.
#
# In the latter, the given block is called with the generator
# itself, and expected to call the +yield+ method for each element.
def initialize(enum = nil, &block)
if enum
@block = proc { |g|
enum.each { |x| g.yield x }
}
else
@block = block
end
@index = 0
@queue = []
@cont_next = @cont_yield = @cont_endp = nil
if @cont_next = callcc { |c| c }
@block.call(self)
@cont_endp.call(nil) if @cont_endp
end
self
end
# Yields an element to the generator.
def yield(value)
if @cont_yield = callcc { |c| c }
@queue << value
@cont_next.call(nil)
end
self
end
# Returns true if the generator has reached the end.
def end?()
if @cont_endp = callcc { |c| c }
@cont_yield.nil? && @queue.empty?
else
@queue.empty?
end
end
# Returns true if the generator has not reached the end yet.
def next?()
!end?
end
# Returns the current index (position) counting from zero.
def index()
@index
end
# Returns the current index (position) counting from zero.
def pos()
@index
end
# Returns the element at the current position and moves forward.
def next()
if end?
raise EOFError, "no more elements available"
end
if @cont_next = callcc { |c| c }
@cont_yield.call(nil) if @cont_yield
end
@index += 1
@queue.shift
end
# Returns the element at the current position.
def current()
if @queue.empty?
raise EOFError, "no more elements available"
end
@queue.first
end
# Rewinds the generator.
def rewind()
initialize(nil, &@block) if @index.nonzero?
self
end
# Rewinds the generator and enumerates the elements.
def each
rewind
until end?
yield self.next
end
self
end
end
class Enumerable::Enumerator
def __generator
@generator ||= Generator.new(self)
end
private :__generator
# call-seq:
# e.next => object
#
# Returns the next object in the enumerator, and move the internal
# position forward. When the position reached at the end, internal
# position is rewinded then StopIteration is raised.
#
# Note that enumeration sequence by next method does not affect other
# non-external enumeration methods, unless underlying iteration
# methods itself has side-effect, e.g. IO#each_line.
#
# Caution: This feature internally uses Generator, which uses callcc
# to stop and resume enumeration to fetch each value. Use with care
# and be aware of the performance loss.
def next
g = __generator
return g.next unless g.end?
g.rewind
raise StopIteration, 'iteration reached at end'
end
# call-seq:
# e.rewind => e
#
# Rewinds the enumeration sequence by the next method.
def rewind
__generator.rewind
self
end
end
#
# SyncEnumerator creates an Enumerable object from multiple Enumerable
# objects and enumerates them synchronously.
#
# == Example
#
# require 'generator'
#
# s = SyncEnumerator.new([1,2,3], ['a', 'b', 'c'])
#
# # Yields [1, 'a'], [2, 'b'], and [3,'c']
# s.each { |row| puts row.join(', ') }
#
class SyncEnumerator
include Enumerable
# Creates a new SyncEnumerator which enumerates rows of given
# Enumerable objects.
def initialize(*enums)
@gens = enums.map { |e| Generator.new(e) }
end
# Returns the number of enumerated Enumerable objects, i.e. the size
# of each row.
def size
@gens.size
end
# Returns the number of enumerated Enumerable objects, i.e. the size
# of each row.
def length
@gens.length
end
# Returns true if the given nth Enumerable object has reached the
# end. If no argument is given, returns true if any of the
# Enumerable objects has reached the end.
def end?(i = nil)
if i.nil?
@gens.detect { |g| g.end? } ? true : false
else
@gens[i].end?
end
end
# Enumerates rows of the Enumerable objects.
def each
@gens.each { |g| g.rewind }
loop do
count = 0
ret = @gens.map { |g|
if g.end?
count += 1
nil
else
g.next
end
}
if count == @gens.size
break
end
yield ret
end
self
end
end
if $0 == __FILE__
eval DATA.read, nil, $0, __LINE__+4
end
__END__
require 'test/unit'
class TC_Generator < Test::Unit::TestCase
def test_block1
g = Generator.new { |g|
# no yield's
}
assert_equal(0, g.pos)
assert_raises(EOFError) { g.current }
end
def test_block2
g = Generator.new { |g|
for i in 'A'..'C'
g.yield i
end
g.yield 'Z'
}
assert_equal(0, g.pos)
assert_equal('A', g.current)
assert_equal(true, g.next?)
assert_equal(0, g.pos)
assert_equal('A', g.current)
assert_equal(0, g.pos)
assert_equal('A', g.next)
assert_equal(1, g.pos)
assert_equal(true, g.next?)
assert_equal(1, g.pos)
assert_equal('B', g.current)
assert_equal(1, g.pos)
assert_equal('B', g.next)
assert_equal(g, g.rewind)
assert_equal(0, g.pos)
assert_equal('A', g.current)
assert_equal(true, g.next?)
assert_equal(0, g.pos)
assert_equal('A', g.current)
assert_equal(0, g.pos)
assert_equal('A', g.next)
assert_equal(1, g.pos)
assert_equal(true, g.next?)
assert_equal(1, g.pos)
assert_equal('B', g.current)
assert_equal(1, g.pos)
assert_equal('B', g.next)
assert_equal(2, g.pos)
assert_equal(true, g.next?)
assert_equal(2, g.pos)
assert_equal('C', g.current)
assert_equal(2, g.pos)
assert_equal('C', g.next)
assert_equal(3, g.pos)
assert_equal(true, g.next?)
assert_equal(3, g.pos)
assert_equal('Z', g.current)
assert_equal(3, g.pos)
assert_equal('Z', g.next)
assert_equal(4, g.pos)
assert_equal(false, g.next?)
assert_raises(EOFError) { g.next }
end
def test_each
a = [5, 6, 7, 8, 9]
g = Generator.new(a)
i = 0
g.each { |x|
assert_equal(a[i], x)
i += 1
break if i == 3
}
assert_equal(3, i)
i = 0
g.each { |x|
assert_equal(a[i], x)
i += 1
}
assert_equal(5, i)
end
end
class TC_SyncEnumerator < Test::Unit::TestCase
def test_each
r = ['a'..'f', 1..10, 10..20]
ra = r.map { |x| x.to_a }
a = (0...(ra.map {|x| x.size}.max)).map { |i| ra.map { |x| x[i] } }
s = SyncEnumerator.new(*r)
i = 0
s.each { |x|
assert_equal(a[i], x)
i += 1
break if i == 3
}
assert_equal(3, i)
i = 0
s.each { |x|
assert_equal(a[i], x)
i += 1
}
assert_equal(a.size, i)
end
end
Jump to Line
Something went wrong with that request. Please try again.