8

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.

Community
  • 1
  • 1
Esteban Araya
  • 29,284
  • 24
  • 107
  • 141
  • Just so we are clear on this: even though Python has some functional constructs, like list comprehensions, it is really not a functional language, and this is not a problem you would easily solve in a true functional style in Python. – Thomas Wouters Sep 23 '08 at 16:22
  • I don't get the requirements. Is it to take N values and locate the distinct values in that set? Or is it to locate a set of some size that has N distinct values? – S.Lott Sep 23 '08 at 16:27
  • If you take(5) and then distinct... you could wind up with 1 number. – Amy B Sep 23 '08 at 16:40
  • @David: Yeah, I just changed the order of those. Thanks! – Esteban Araya Sep 23 '08 at 16:42
  • 1
    I'll bet it's the implication the LINQ is so clearly superior that no one can ever produce Ruby or Python that matches your lofty standards of "elegance". Just guessing. The question is lame, but not *that* lame. – S.Lott Sep 23 '08 at 16:46
  • I really didn't mean to imply that - I just started learning Ruby yesterday, and really had no clue how to do this. I'm sorry if I came across that way. – Esteban Araya Sep 23 '08 at 16:57

14 Answers14

13
>>> 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
  • 1
  • 85
  • 340
  • 366
  • 3
    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 Sep 23 '08 at 16:21
  • Why would the samples be unique? There doesn't seem to be a filter of any kind to assure uniqueness. – S.Lott Sep 23 '08 at 16:26
  • 1
    The random.sample() function does it itself. "Return a k length list of unique elements chosen from the population sequence". – Jeremy Sep 23 '08 at 16:33
5

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) 
horseyguy
  • 29,455
  • 20
  • 103
  • 145
Michael Deardeuff
  • 10,386
  • 5
  • 51
  • 74
3

Hmm... How about (Python):

s = set()
while len(s) <= N: s.update((random.random(),))
Will Boyce
  • 11,663
  • 1
  • 17
  • 15
2

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
  • 130,178
  • 23
  • 148
  • 122
2

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
  • 11,922
  • 5
  • 40
  • 44
  • 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 Sep 23 '08 at 16:37
2

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]
Bite code
  • 578,959
  • 113
  • 301
  • 329
  • If you have a duplicate removed, won't you end up with fewer values than desired? – Jeremy Sep 23 '08 at 16:40
  • Yes, but he did the same in his question, so it's an exact translation. That's what is asked for : a translation. – Bite code Sep 23 '08 at 16:42
  • He didn't - the .Take(5) occurs after the .Distinct call, so will be drawing 5 items from an already uniqified sequence. – Brian Sep 23 '08 at 19:40
  • Never mind - just read the comment on the question, he fixed the order of .Distinct / .Take since posting – Brian Sep 23 '08 at 20:04
1

In Ruby 1.9:

Array(0..100).sample(5)
horseyguy
  • 29,455
  • 20
  • 103
  • 145
0

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 Lenski
  • 76,929
  • 13
  • 76
  • 124
0
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
  • 14,735
  • 5
  • 36
  • 39
0

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
  • 10,838
  • 6
  • 47
  • 58
  • Nice. Just a little comment: You should use a set() instead of a hash in Distince and ToArray can use list(). – olt Sep 23 '08 at 20:46
0

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
  • 384,516
  • 81
  • 508
  • 779
0

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()
user19087
  • 770
  • 5
  • 6
0

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
  • 116,865
  • 28
  • 107
  • 112
-1

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
  • 388
  • 2
  • 10
  • True: I realize that Take & Distinct are probably looping behind the scenes. What I meant was no loop that you'd have to write... I do like your second solutions nonetheless. Thanks! – Esteban Araya Sep 23 '08 at 16:45
  • `(rand * max).to_i` should be written as `rand max` – user102008 Mar 17 '11 at 08:30