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.

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.

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’.

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.