Here's my solution. Mine is pretty much similar in spirit to Caleb's
solution, except that we use different lock mechanisms. My first take
was to replace the Continuation with a thread and a SizedQueue. That is,
in the #initialize, I create a new thread with the given block (or the
one I generate with the given enum), which will eventually be blocked
when writing to the queue with the size of 1 in #yield. Once #next
dequeues the head, the #yield thread continues, etc.
This passed James's TC_Generator test suite, but miserably failed on
Luke's "shared state" test, although the code was supposed to handle the
case.
It turned out, if SizedQueue of size one is the only blocking mechanism,
you have two values waiting at the queue's door; one in the queue, the
other right before the queue, waiting to be unlocked. This made the
reponse to Luke's test 1, 1, 2, 3, 4 (and then 10, 10, 10, ... if I
increase the repetition). I needed to make the thread stop right after
it enqueued the value until #next consumes it.
My solution was to get rid of SizedQueue and to use a Mutex and a
ConditionVariable to accomplish just that. At that point I saw Caleb's
solution and thought that starting and stopping a thead should be much
slower than using Mutexes and Cond_Vars. To my surprise, that wasn't the
case. Mutex mechanism was much slower than Caleb's thread switching
solution.
Anyways, here's the code. Benchmark follows the code (I ran on a slower
notebook).
Thanks James for the nice quiz.
Jesse
#!/usr/bin/env ruby
require 'thread'
class Generator
include Enumerable
def initialize(enum = nil, &block)
if enum
@block = proc { |g|
enum.each { |x| g.yield x }
}
else
@block = block
end
@index = 0
@queue = []
@q_access = Mutex.new
@q_consumed = ConditionVariable.new
@thread = Thread.new(self, &@block)
self
end
def yield(value)
@q_access.synchronize {
@queue << value
@q_consumed.wait(@q_access)
}
self
end
def end?()
Thread.pass while @queue.empty? && @thread.alive?
@queue.empty? && !@thread.alive?
end
def next?()
!end?
end
def index()
@index
end
def pos()
@index
end
def next()
if end?
raise EOFError, "no more elements available"
end
ret = nil
@q_access.synchronize {
@index += 1
ret = @queue.shift
@q_consumed.signal
}
ret
end
def current()
if end?
raise EOFError, "no more elements available"
end
@queue.first
end
def rewind()
initialize(nil, &@block) if @index.nonzero?
self
end
def each
rewind
until end?
yield self.next
end
self
end
end
###
### tests = 10
### enum = (1..1000).to_a
###
### Construction ###
Rehearsal
----------------------------------------------------------------
Old callcc Generator 0.000000 0.000000 0.000000 (
0.003000)
Caleb's SynchronousGenerator 0.000000 0.000000 0.000000 (
0.003000)
Jesse's FasterGenerator 0.010000 0.010000 0.020000 (
0.016000)
------------------------------------------------------- total:
0.020000sec
user system total
real
Old callcc Generator 0.000000 0.010000 0.010000 (
0.003000)
Caleb's SynchronousGenerator 0.000000 0.000000 0.000000 (
0.002000)
Jesse's FasterGenerator 0.000000 0.000000 0.000000 (
0.003000)
### next() ###
Rehearsal
----------------------------------------------------------------
Old callcc Generator 4.116000 0.270000 4.386000 (
4.438000)
Caleb's SynchronousGenerator 1.181000 0.010000 1.191000 (
1.194000)
Jesse's FasterGenerator 2.674000 0.000000 2.674000 (
2.831000)
------------------------------------------------------- total:
8.251000sec
user system total
real
Old callcc Generator 4.066000 0.010000 4.076000 (
4.099000)
Caleb's SynchronousGenerator 1.212000 0.000000 1.212000 (
1.222000)
Jesse's FasterGenerator 2.704000 0.000000 2.704000 (
2.706000)
--
Posted via http://www.ruby-forum.com/.