Matthew Moss wrote:
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
> 
> The three rules of Ruby Quiz 2:
> 
> 1.  Please do not post any solutions or spoiler discussion for this
> quiz until 48 hours have passed from the time on this message.
> 
> 2.  Support Ruby Quiz 2 by submitting ideas as often as you can! (A
> permanent, new website is in the works for Ruby Quiz 2. Until then,
> please visit the temporary website at
> 
>      <http://splatbang.com/rubyquiz/>.
> 
> 3.  Enjoy!
> 
> Suggestion:  A [QUIZ] in the subject of emails about the problem
> helps everyone on Ruby Talk follow the discussion.  Please reply to
> the original quiz message, if you can.
> 
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
> 
> ## Not So Random (#173)
> 
> As part of Ruby's standard library, we have access to a good
> pseudorandom number generator (PRNG), the [Mersenne twister][1]. (In
> particular, the MT19937 variant.) Ruby provides two kernel functions
> to make use of this generator: `srand` and `rand`. This week's quiz
> involves generating random numbers in a predictable manner.
> 
> The first part is to create a wrapper around the random number
> generator `rand`, supporting the appropriate parameter (i.e. the
> parameter intrinsic to `rand`). Your wrapper should implement two
> additional functions: `next` which returns the next random number, and
> `reset` which restarts the sequence. So, for example:
> 
>     irb> x = Random.new(100)
>     => #<Random:...>
> 
>     irb> Array.new(5) { x.next }
>     => [51, 92, 14, 71, 60]
> 
>     irb> Array.new(5) { x.next }
>     => [20, 82, 86, 74, 74]
> 
>     irb> x.reset
>     => nil
> 
>     irb> Array.new(5) { x.next }
>     => [51, 92, 14, 71, 60]     # after reset, sequence restarts
> 
> You may do this as a class, as depicted here, or as a function,
> lambda, code block... whatever you like.
> 
> The second part is a little trickier: creating multiple, concurrent,
> _reproducible_ sequences of pseudorandom numbers isn't so easy. As
> convenient as the built-in generator is, there is only one seed. For
> example, assume we have two `Random` objects, created as shown above,
> `x` and `y`.
> 
>     irb> x = Random.new(100)
>     => #<Random:...>
> 
>     irb> Array.new(6) { x.next }
>     => [51, 92, 14, 71, 60, 20]
> 
>     irb> y = Random.new(100)
>     => #<random:...>
> 
>     irb> Array.new(6) { y.next }
>     => [66, 92, 98, 17, 83, 57]
> 
>     irb> x.reset
>     => nil
> 
>     irb> Array.new(2) { x.next }
>     => [51, 92]       # ok: sequence restarted as requested
> 
>     irb> Array.new(2) { y.next }
>     => [14, 71]       # fail: this is part of _x_ sequence
> 
>     irb> Array.new(2) { x.next }
>     => [60, 20]       # more fail: _x_ is now [51, 92, 60, 20]? wrong...
> 
> 
> The reason for the failure should be obvious: my current
> implementation of `Random` just blindly uses `srand` and `rand`
> without considering that there may be multiple instances of `Random`.
> 
> So, for this second part, expand your wrapper to support concurrent
> use. Please note that you are required to make use of the built-in
> generator: you should not implement your own PRNG.
> 
> One final note... It is up to you whether the seed for each wrapper
> will be user-settable or hidden (as in my examples above). However, if
> hidden, each wrapper should have a different seed. (Generated
> randomly, perhaps?)
> 
> 
> 
> [1]: http://en.wikipedia.org/wiki/Mersenne_twister
> 
> 
>


Without knowing how the PRNG is implemented (even if in C or Ruby), this 
solution solves the problem.  It'll be slow if you're switching between 
generators deep in the sequence, but it works.  It also accounts for 
Kernel#rand and Kernel#srand screwing up your Random objects.  It passes 
the tests posted by brabuhr / gmail.com.

module Kernel
   alias_method :old_rand, :rand
   alias_method :old_srand, :srand

   def rand(*a)
     Random.dirty
     old_rand(*a)
   end

   def srand(*a)
     Random.dirty
     old_srand(*a)
   end
end

class Random
   @@seed = nil

   def self.dirty
     @@seed = nil
   end

   def initialize(range, seed = Time.now.to_i)
     @range = range
     @seed = seed
     reset
   end

   def reset
     @sequence = 0
     reinitialize
   end

   def reinitialize
     old_srand(@seed)
     @@seed = @seed
     @sequence.times { old_rand(@range) }
   end

   def next
     reinitialize if @@seed.nil? or @@seed != @seed
     @sequence += 1
     old_rand(@range)
   end
end


-- 
Michael Morin
Guide to Ruby
http://ruby.about.com/
Become an About.com Guide:  beaguide.about.com
About.com is part of the New York Times Company