Regarding rand

I like it! Two notes:

1. Use #respond_to? instead of ::method_defined? This allows for singleton methods.*

2. What other people are saying about the cost of creating an array each time. You might use each_with_index (which is stupidly slow for Arrays), or might implement a different rand method for Range, Array, etc.

Devin

···

I was reading Learn to Program by Chris Pine yesterday, in one bit it says:
  ' Note that I used rand(101) to get back numbers from 0 to 100, and
that rand(1) always gives back 0.'

I was wondering if rand() could support syntax like:
   rand(0..100)

Here's my go:

def random(value)
  if value.class.method_defined? 'entries'
    entries = value.entries
    entries[rand(entries.length)]
  else
    rand(value)
  end
end

this works with arrays and hashes too.

I'm coming to Ruby via RoR and i'm loving it, I should be writing a
web app and i'm playing with Ruby instead.

Ben

Thanks guys,

Daniel, I started out with something similar to that, although yours
is alot better, but then thought it would be nice to have something
like random 'a'..'z' aswell or even random Thing.new(1)..Thing.new(6)
. Which i'll probably not need anyway so i'll 'borrow' yours if I may.

Jacob, I'm new to Ruby but seeing code like that fills me with a
feeling of well-being.

Devin, thanks, 1) will do!, i'm going to go and have a look at the
pickaxe to see why. 2) I was vaguely aware that creating the arrays
could be a bad idea for big ranges but couldn't work out how to get
the nth member of a range except by doing something like

value = range.first
for 1..n
  value = value.succ
end

which seemed a bit nasty or by getting the entries which seemed quite
nice in light of the chapter I read about duck typing

Ben (Ruby Newbie)

···

On 7/19/05, twifkak@comcast.net <twifkak@comcast.net> wrote:

I like it! Two notes:

1. Use #respond_to? instead of ::method_defined? This allows for singleton methods.*

2. What other people are saying about the cost of creating an array each time. You might use each_with_index (which is stupidly slow for Arrays), or might implement a different rand method for Range, Array, etc.

Devin

> I was reading Learn to Program by Chris Pine yesterday, in one bit it says:
> ' Note that I used rand(101) to get back numbers from 0 to 100, and
> that rand(1) always gives back 0.'
>
> I was wondering if rand() could support syntax like:
> rand(0..100)
>
> Here's my go:
>
> def random(value)
> if value.class.method_defined? 'entries'
> entries = value.entries
> entries[rand(entries.length)]
> else
> rand(value)
> end
> end
>
> this works with arrays and hashes too.
>
> I'm coming to Ruby via RoR and i'm loving it, I should be writing a
> web app and i'm playing with Ruby instead.
>
> Ben
>

Hi Ben,

Daniel, I started out with something similar to that,
although yours is alot better, but then thought it would
be nice to have something like random 'a'..'z' aswell

That could be useful, at least for single-character strings.

Luckily, there was a just quiz about random choosing, so we
can steal the winning algorithm from there. :slight_smile:

Here's a more extensive implementation:

   def random(object)
     if object.kind_of? Numeric
       if object.integer?
         rand(object)
       else
         rand * object
       end
     else
       object.random
     end
   end

   # Inefficient fallback implementation.
   module Enumerable
     def random(n=nil) to_a.random(n) end
   end

   class Range
     def bounds ; [self.begin, self.end] end
     def lower_bound ; bounds.sort.first end
     def upper_bound ; bounds.sort.last end
     def numeric? ; bounds.all? { |x| x.kind_of? Numeric } end
     def integral? ; numeric? and bounds.all? { |x| x.integer? } end
     def include_end? ; not exclude_end? end

     def size
       d = upper_bound - lower_bound
       if include_end? and d.respond_to? :succ
       then d.succ else d end
     end

     def random(n=nil)
       if integral?
         if n.nil?
           rand(size) + lower_bound
         else
           n.integer? or raise ArgumentError,
             "can't choose a non-integral (#{n}) number of elements"
           n <= size or raise ArgumentError,
             "can't choose #{n} out of #{size} elements"
           n >= 0 or raise ArgumentError,
             "can't choose a negative (#{n}) number of elements"
           hash = {}
           hash[random] = true while hash.size < n
           hash.keys.sort_by { rand }
         end
       elsif numeric?
         if n.nil?
           rand * size + lower_bound
         else
           values =
           n.times { values << random }
           values
         end
       else
         super
       end
     end
   end

   class Array
     def random(n=nil)
       if n.nil?
         self[rand(size)]
       else
         values_at(*(0...size).random(n)).sort_by { rand }
       end
     end
   end

There's some overkill randomization going on in Array#random
where we first grab n random numbers in random order, and
then select the values at those indices and randomize
*their* order. It would be sufficient to grab n random
numbers in whatever order (not necessarily random), select
the values at those indices, and then randomize the result.

If you populate a hash with random float keys, I'm not sure
in what order the keys come out when you call Hash#keys.
Could the order be random? I assume not, but if that's so,
we could get rid of the ‘sort_by { rand }’ in Range#random.

Actually, I'm not even sure whether Array#random needs to
return the elements in random order. Maybe it could return
them in whatever order is most convenient, and leave it to
the user to randomize or sort the result according to need.

Oh, and we could add this convenience method:

   module Enumerable
     def shuffle
       sort_by { rand }
     end
   end

So you'd do one of these:

  * foo.random(4) # Order doesn't matter.

  * foo.random(4).sort # Increasing order.

  * foo.random(4).shuffle # Random order.

Okay, I'm convinced — let's change Array#random accordingly:

   class Array
     def random(n=nil)
       if n.nil?
         self[rand(size)]
       else
         values_at(*(0...size).random(n))
       end
     end
   end

I think my implementation of Hash#random is pretty much as
good as it gets, but if anyone can think of a better way,
please share it. I don't particularly like the way I'm
first building an array, then flattening it, splatting it,
and finally turning it back into a hash. :slight_smile:

Here it is, anyway:

   class Hash
     def pair_at(key) [key, self[key]] end
     def pairs_at(*keys) keys.collect { |k| pair_at k } end
     def random(n=nil)
       if n.nil?
         pair_at(keys.random)
       else
         Hash[*pairs_at(*keys.random(n)).flatten]
       end
     end
   end

I think returning a subhash (except when n is nil) seems the
most useful, since if you just want some random values or
keys from the hash, that's easy:

   hash.values.random(123) hash.keys.random(123)

It's easy to adapt other collections to this API:

   class Set
     def random(n=nil) Set[*super] end
   end

Which i'll probably not need anyway so i'll 'borrow' yours
if I may.

Of course, help yourself. You may want to put all this in a
separate file — say, ‘random.rb’. :slight_smile:

The above code will let you do all these:

   random(42) #=> 13 random(2.0) #=> 1.9656...

   random(1..9) #=> 5 random(2.3..5.8) #=> 3.1246...

   random("a".."z") #=> "f" random("a".."zz") #=> "ge"

   [1, 1, 2, 3, 5, 8].random #=> 2

   [1, 1, 2, 3, 5, 8].random(3) #=> [1, 1, 5]

   {:a=>1, :b=>2, :c=>3}.random #=> [:b, 2]

   {:a=>1, :b=>2, :c=>3}.random(2) #=> {:a=>1, :c=>3}

   Set[1, 2, 3, "x", "y", "z"].random(3) #=> Set[2, 3, "y"]

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.

why not bundle this up and post on the raa? i'd like to

   require 'random'

and have some nice things added.

cheers.

-a

···

On Wed, 20 Jul 2005, Daniel Brockman wrote:

Hi Ben,

Daniel, I started out with something similar to that,
although yours is alot better, but then thought it would
be nice to have something like random 'a'..'z' aswell

That could be useful, at least for single-character strings.

Luckily, there was a just quiz about random choosing, so we
can steal the winning algorithm from there. :slight_smile:

Here's a more extensive implementation:

  def random(object)
    if object.kind_of? Numeric
      if object.integer?
        rand(object)
      else
        rand * object
      end
    else
      object.random
    end
  end

  # Inefficient fallback implementation.
  module Enumerable
    def random(n=nil) to_a.random(n) end
  end

  class Range
    def bounds ; [self.begin, self.end] end
    def lower_bound ; bounds.sort.first end
    def upper_bound ; bounds.sort.last end
    def numeric? ; bounds.all? { |x| x.kind_of? Numeric } end
    def integral? ; numeric? and bounds.all? { |x| x.integer? } end
    def include_end? ; not exclude_end? end

    def size
      d = upper_bound - lower_bound
      if include_end? and d.respond_to? :succ
      then d.succ else d end
    end

    def random(n=nil)
      if integral?
        if n.nil?
          rand(size) + lower_bound
        else
          n.integer? or raise ArgumentError,
            "can't choose a non-integral (#{n}) number of elements"
          n <= size or raise ArgumentError,
            "can't choose #{n} out of #{size} elements"
          n >= 0 or raise ArgumentError,
            "can't choose a negative (#{n}) number of elements"
          hash = {}
          hash[random] = true while hash.size < n
          hash.keys.sort_by { rand }
        end
      elsif numeric?
        if n.nil?
          rand * size + lower_bound
        else
          values =
          n.times { values << random }
          values
        end
      else
        super
      end
    end
  end

  class Array
    def random(n=nil)
      if n.nil?
        self[rand(size)]
      else
        values_at(*(0...size).random(n)).sort_by { rand }
      end
    end
  end

There's some overkill randomization going on in Array#random
where we first grab n random numbers in random order, and
then select the values at those indices and randomize
*their* order. It would be sufficient to grab n random
numbers in whatever order (not necessarily random), select
the values at those indices, and then randomize the result.

If you populate a hash with random float keys, I'm not sure
in what order the keys come out when you call Hash#keys.
Could the order be random? I assume not, but if that's so,
we could get rid of the ‘sort_by { rand }’ in Range#random.

Actually, I'm not even sure whether Array#random needs to
return the elements in random order. Maybe it could return
them in whatever order is most convenient, and leave it to
the user to randomize or sort the result according to need.

Oh, and we could add this convenience method:

  module Enumerable
    def shuffle
      sort_by { rand }
    end
  end

So you'd do one of these:

* foo.random(4) # Order doesn't matter.

* foo.random(4).sort # Increasing order.

* foo.random(4).shuffle # Random order.

Okay, I'm convinced — let's change Array#random accordingly:

  class Array
    def random(n=nil)
      if n.nil?
        self[rand(size)]
      else
        values_at(*(0...size).random(n))
      end
    end
  end

I think my implementation of Hash#random is pretty much as
good as it gets, but if anyone can think of a better way,
please share it. I don't particularly like the way I'm
first building an array, then flattening it, splatting it,
and finally turning it back into a hash. :slight_smile:

Here it is, anyway:

  class Hash
    def pair_at(key) [key, self[key]] end
    def pairs_at(*keys) keys.collect { |k| pair_at k } end
    def random(n=nil)
      if n.nil?
        pair_at(keys.random)
      else
        Hash[*pairs_at(*keys.random(n)).flatten]
      end
    end
  end

I think returning a subhash (except when n is nil) seems the
most useful, since if you just want some random values or
keys from the hash, that's easy:

  hash.values.random(123) hash.keys.random(123)

It's easy to adapt other collections to this API:

  class Set
    def random(n=nil) Set[*super] end
  end

Which i'll probably not need anyway so i'll 'borrow' yours
if I may.

Of course, help yourself. You may want to put all this in a
separate file — say, ‘random.rb’. :slight_smile:

The above code will let you do all these:

  random(42) #=> 13 random(2.0) #=> 1.9656...

  random(1..9) #=> 5 random(2.3..5.8) #=> 3.1246...

  random("a".."z") #=> "f" random("a".."zz") #=> "ge"

  [1, 1, 2, 3, 5, 8].random #=> 2

  [1, 1, 2, 3, 5, 8].random(3) #=> [1, 1, 5]

  {:a=>1, :b=>2, :c=>3}.random #=> [:b, 2]

  {:a=>1, :b=>2, :c=>3}.random(2) #=> {:a=>1, :c=>3}

  Set[1, 2, 3, "x", "y", "z"].random(3) #=> Set[2, 3, "y"]

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
My religion is very simple. My religion is kindness.
--Tenzin Gyatso

===============================================================================

Devin, thanks, 1) will do!, i'm going to go and have a look at the
pickaxe to see why.

Yes, singleton methods are the elixir of life.

Ooh, i'm liking singleton methods, I'm guessing they make a lot of
rails easier/possible.

Here's a more extensive implementation:

Blimey, you really meant that, thanks!

Of course, help yourself. You may want to put all this in a
separate file — say, 'random.rb'. :slight_smile:

Done :slight_smile:

Thanks Guys,
Ben