views:

652

answers:

14

Yesterday, I asked this question and never really got an answer I was really happy with. I really would like to know how to generate a list of N unique random numbers using a functional language such as Ruby without having to be extremely imperative in style.

Since I didn't see anything I really liked, I've written the solution I was looking for in LINQ:


       static void Main(string[] args)
     {
      var temp = from q in GetRandomNumbers(100).Distinct().Take(5) select q;
     }

     private static IEnumerable GetRandomNumbers(int max)
     {
      Random r = new Random();
      while (true)
      {
       yield return r.Next(max);
      }
     }

Can you translate my LINQ to Ruby? Python? Any other functional programming language?

Note: Please try not to use too many loops and conditionals - otherwise the solution is trivial. Also, I'd rather see a solution where you don't have to generate an array much bigger than N so you can then just remove the duplicates and trim it down to N.

I know I'm being picky, but I'd really like to see some elegant solutions to this problem. Thanks!

Edit:
Why all the downvotes?

Originally my code sample had the Distinct() after the Take() which, as many pointed out, could leave me with an empty list. I've changed the order in which those methods are called to reflect what I meant in the first place.

Apology:
I've been told this post came across as rather snobbish. I wasn't trying to imply that LINQ is better than Ruby/Python; or that my solution is much better than everyone else's. My intent is just to learn how to do this (with certain constraints) in Ruby. I'm sorry if I came across as a jerk.

A: 

Python with Numeric Python:

from numpy import *
a = random.random_integers(0, 100, 5)
b = unique(a)

Voilà! Sure you could do something similar in a functional programming style but... why?

Dan
Because this does not use iterators and store all the integers in memory.
e-satis
+2  A: 

I will forgo the simplest solutions using the 'random' module since I take it that's not really what you are after. Here's what I think you are looking for in Python:

>>> import random
>>> 
>>> def getUniqueRandomNumbers(num, highest):
...     seen = set()
...     while len(seen) < num:
...         i = random.randrange(0, highest)
...         if i not in seen:
...             seen.add(i)  
...             yield i
... 
>>>

To show you how it works:

>>> list(getUniqueRandomNumbers(10, 100))
[81, 57, 98, 47, 93, 31, 29, 24, 97, 10]
Thomas Wouters
+12  A: 
>>> import random
>>> print random.sample(xrange(100), 5)
[61, 54, 91, 72, 85]

This should yield 5 unique values in the range 0 — 99. The xrange object generates values as requested so no memory is used for values that aren't sampled.

Jeremy Banks
xrange() does in fact not use a generator. it's a fake list. Generators are not sequences and can't be indexed, so random.sample() would fail if it were one.
Thomas Wouters
Right, of course, thank you. Corrected.
Jeremy Banks
Why would the samples be unique? There doesn't seem to be a filter of any kind to assure uniqueness.
S.Lott
The random.sample() function does it itself. "Return a k length list of unique elements chosen from the population sequence".
Jeremy Banks
A: 

I can't really read your LINQ, but I think you're trying to get 5 random numbers up to 100 and then remove duplicates.

Here's a solution for that:

def random(max)
    (rand * max).to_i
end

# Get 5 random numbers between 0 and 100
a = (1..5).inject([]){|acc,i| acc << random( 100)}
# Remove Duplicates
a = a & a

But perhaps you're actually looking for 5 distinct random numbers between 0 and 100. In which case:

def random(max)
    (rand * max).to_i
end

a = []
while( a.size < 5)
    a << random( 100)
    a = a & a
end

Now, this one might violate your sense of "not too many loops," but presumably Take and Distinct are just hiding the looping from you. It would be easy enough to just add methods to Enumerable to hide the while loop.

hjdivad
Esteban Araya
+3  A: 

Hmm... How about (Python):

s = set()
while len(s) <= N: s.update((random.random(),))
Will Boyce
why not use set.add?
Torsten Marek
My bad! Thanks for pointing that out ;-)
Will Boyce
+4  A: 

In Ruby:

a = (0..100).entries.sort_by {rand}.slice! 0, 5

Update: Here is a slightly different way: a = (0...100).entries.sort_by{rand}[0...5]

EDIT:

and In Ruby 1.9 you can do this:

Array(0..100).sample(5) 
Michael Deardeuff
+1  A: 

Here's another Ruby solution:

a = (1..5).collect { rand(100) }
a & a

I think, with your LINQ statement, the Distinct will remove duplicates after 5 have already been taken, so you aren't guaranteed to get 5 back. Someone can correct me if I'm wrong, though.

David Mohundro
Yes, I was worried about that too. However, I haven't been able to produce an array with less than 5 elements yet. Doesn't mean it could happen, however. Worst case scenario though, I could call Distinct() beofre Take(), right?
Esteban Araya
A: 
import random

def makeRand(n):
   rand = random.Random()
   while 1:
      yield rand.randint(0,n)
   yield rand.randint(0,n)      

gen = makeRand(100)      
terms = [ gen.next() for n in range(5) ]

print "raw list"
print terms
print "de-duped list"
print list(set(terms))

# produces output similar to this
#
# raw list
# [22, 11, 35, 55, 1]
# de-duped list
# [35, 11, 1, 22, 55]
Joe Skora
A: 

Well, first you rewrite LINQ in Python. Then your solution is a one-liner :)

from random import randrange

def Distinct(items):
    set = {}
    for i in items:
        if not set.has_key(i):
            yield i
            set[i] = 1

def Take(num, items):
    for i in items:
        if num > 0:
            yield i
            num = num - 1
        else:
            break

def ToArray(items):
    return [i for i in items]

def GetRandomNumbers(max):
    while 1:
        yield randrange(max)

print ToArray(Take(5, Distinct(GetRandomNumbers(100))))

If you put all the simple methods above into a module called LINQ.py, you can impress your friends.

(Disclaimer: of course, this is not actually rewriting LINQ in Python. People have the misconception that LINQ is just a bunch of trivial extension methods and some new syntax. The really advanced part of LINQ, however, is automatic SQL generation so that when you're querying a database, it's the database that implements Distinct() rather than the client side.)

apenwarr
Nice. Just a little comment: You should use a set() instead of a hash in Distince and ToArray can use list().
olt
+2  A: 

EDIT : Ok, just for fun, a shorter and faster one (and still using iterators).

def getRandomNumbers(max, size) :
    pool = set()
    return ((lambda x :  pool.add(x) or x)(random.randrange(max)) for x in xrange(size) if len(a) < size)

print [x for x in gen(100, 5)]
[0, 10, 19, 51, 18]

Yeah, I know, one-liners should be left to perl lovers, but I think this one is quite powerful isn't it ?

Old message here :

My god, how complicated is all that ! Let's be pythonic :

import random
def getRandomNumber(max, size, min=0) :
   # using () and xrange = using iterators
   return (random.randrange(min, max) for x in xrange(size))

print set(getRandomNumber(100, 5)) # set() removes duplicates
set([88, 99, 29, 70, 23])

Enjoy

EDIT : As commentators noticed, this is an exact translation of the question's code.

To avoid the problem we got by removing duplicates after generating the list, resulting in too little data, you can choose another way :

def getRandomNumbers(max, size) :
    pool = []
    while len(pool) < size :
        tmp = random.randrange(max)
        if tmp not in pool :
            yield pool.append(tmp) or tmp

print [x for x in getRandomNumbers(5, 5)]
[2, 1, 0, 3, 4]
e-satis
If you have a duplicate removed, won't you end up with fewer values than desired?
Jeremy Banks
Yes, but he did the same in his question, so it's an exact translation. That's what is asked for : a translation.
e-satis
He didn't - the .Take(5) occurs after the .Distinct call, so will be drawing 5 items from an already uniqified sequence.
Brian
Never mind - just read the comment on the question, he fixed the order of .Distinct / .Take since posting
Brian
A: 

Here's a transliteration from your solution to Python.

First, a generator that creates Random numbers. This isn't very Pythonic, but it's a good match with your sample code.

>>> import random
>>> def getRandomNumbers( max ):
...     while True:
...             yield random.randrange(0,max)

Here's a client loop that collects a set of 5 distinct values. This is -- again -- not the most Pythonic implementation.

>>> distinctSet= set()
>>> for r in getRandomNumbers( 100 ):
...     distinctSet.add( r )
...     if len(distinctSet) == 5: 
...             break
... 
>>> distinctSet
set([81, 66, 28, 53, 46])

It's not clear why you want to use a generator for random numbers -- that's one of the few things that's so simple that a generator doesn't simplify it.

A more Pythonic version might be something like:

distinctSet= set()
while len(distinctSet) != 5:
    distinctSet.add( random.randrange(0,100) )

If the requirements are to generate 5 values and find distinct among those 5, then something like

distinctSet= set( [random.randrange(0,100) for i in range(5) ] )
S.Lott
A: 

Maybe this will suit your needs and look a bit more linqish:

from numpy import random,unique

def GetRandomNumbers(total=5):
    while True:
     yield unique(random.random(total*2))[:total]

randomGenerator = GetRandomNumbers()

myRandomNumbers = randomGenerator.next()
Doesn't work with the standard library.
e-satis
A: 

Here's another python version, more closely matching the structure of your C# code. There isn't a builtin for giving distinct results, so I've added a function to do this.

import itertools, random

def distinct(seq):
    seen=set()
    for item in seq:
        if item not in seen:
            seen.add(item)
            yield item

def getRandomNumbers(max):
    while 1:
        yield random.randint(0,max)

for item in itertools.islice(distinct(getRandomNumbers(100)), 5):
    print item
Brian
A: 

In Ruby 1.9:

Array(0..100).sample(5)
banister