tips for this exercise?

J

John Salerno

I'm working on another exercise now about generating random numbers for
the lottery. What I want to do is write a function that picks 5 random
numbers from 1-53 and returns them. Here's what I have so far:

numbers = range(1, 54)

def genNumbers():
for x in range(5):
fiveNumbers = []
number = random.choice(numbers)
numbers.remove(number)
fiveNumbers = fiveNumbers.append(number)
return fiveNumbers

Other than being sort of ugly, this also has the side effect of actually
editing the original list, which I don't want since I will want to
generate more than one set of numbers.

Is there a better way to extract a certain number of items from a list
(so maybe I don't need the for loop)? Is a list even the right type to
use, since it gets edited in place? Perhaps a set?
 
B

Brian Quinlan

John said:
I'm working on another exercise now about generating random numbers for
the lottery. What I want to do is write a function that picks 5 random
numbers from 1-53 and returns them. Here's what I have so far:

numbers = range(1, 54)

def genNumbers():
for x in range(5):
fiveNumbers = []
number = random.choice(numbers)
numbers.remove(number)
fiveNumbers = fiveNumbers.append(number)
return fiveNumbers

Other than being sort of ugly, this also has the side effect of actually
editing the original list, which I don't want since I will want to
generate more than one set of numbers.

Is there a better way to extract a certain number of items from a list
(so maybe I don't need the for loop)? Is a list even the right type to
use, since it gets edited in place? Perhaps a set?

I would just write the function like this:

def genNumbers():
shuffle_nums = numbers[:] # copy the list to preserve the orginal
# order (if it matters)
random.shuffle(shuffle_nums) # shuffle the entire list
return shuffle_nums[:5] # return the first 5 elements

Cheers,
Brian
 
J

John Salerno

Brian said:
I would just write the function like this:

def genNumbers():
shuffle_nums = numbers[:] # copy the list to preserve the orginal
# order (if it matters)
random.shuffle(shuffle_nums) # shuffle the entire list
return shuffle_nums[:5] # return the first 5 elements

Thanks. Interesting idea. I did consider copying it, but does that hurt
performance each time the function is called? I know it may not be
noticeable, but I don't like the idea of doing unnecessary work like
that, if it is in fact unnecessary.
 
L

Larry Bates

Not a lot wrong with your code except that your
return fiveNumbers will execute the first time
through the loop because it is indented inside
the loop. Also, append does its work in place,
it doesn't return a new list.

You also might want to pass the maximum number
and return count, into the function and initialize
numbers list there. That way you can call
genNumbers(54, 5) or genNumbers(103, 7) and get
differing length lists back from your function.

def genNumbers(maxnum, retcount):
numbers=range(1, maxnum)
for x in range(retcount):
retNumbers = []
number = random.choice(numbers)
numbers.remove(number)
retNumbers.append(number)

return retNumbers


-Larry Bates
 
P

Paul Rubin

John Salerno said:
Is there a better way to extract a certain number of items from a list
(so maybe I don't need the for loop)? Is a list even the right type to
use, since it gets edited in place? Perhaps a set?

See the doc for random.sample.
 
S

Steve M

You have two lines inside the loop that ought to be outside the loop -
the initial assignment to fiveNumbers and the return statement. Also,
the line that appends new numbers to fiveNumbers is not quite correct -
the append() method modifies the list in place, rather than return a
new list.

Here's one that does what you seem to want. It exploits the fact that a
given number can't be in a set twice. It eliminates having to modify
and recreate the numbers list, but incurs the risk of calling choice()
more than five times; however each such extra call to choice is
increasingly improbable...

from random import choice
numbers = range(1,54)

def genNumbers():
fiveNumbers = set()
while len(fiveNumbers) < 5:
fiveNumbers.add(choice(numbers))
return list(fiveNumbers)
 
K

Kent Johnson

John said:
Brian said:
I would just write the function like this:

def genNumbers():
shuffle_nums = numbers[:] # copy the list to preserve the orginal
# order (if it matters)
random.shuffle(shuffle_nums) # shuffle the entire list
return shuffle_nums[:5] # return the first 5 elements


Thanks. Interesting idea. I did consider copying it, but does that hurt
performance each time the function is called? I know it may not be
noticeable, but I don't like the idea of doing unnecessary work like
that, if it is in fact unnecessary.

Yes, it hurts performance. No, you won't notice it. It probably is
unnecessary for your application, since you are just going to shuffle
the list again for the next use.

BUT...worrying about performance at this level is generally a waste of
your time. For a program that picks lottery numbers, performance would
have to be truly awful before you even noticed. Don't optimize until you
have identified a problem.

Python is pretty fast for most of the things you will want to do with it.

Kent
 
S

Steven D'Aprano

Brian said:
I would just write the function like this:

def genNumbers():
shuffle_nums = numbers[:] # copy the list to preserve the orginal
# order (if it matters)
random.shuffle(shuffle_nums) # shuffle the entire list
return shuffle_nums[:5] # return the first 5 elements

Thanks. Interesting idea. I did consider copying it, but does that hurt
performance each time the function is called? I know it may not be
noticeable, but I don't like the idea of doing unnecessary work like
that, if it is in fact unnecessary.

Generally, lottery numbers are selected from a range, e.g. one to forty.
So rather than passing a global list, just pass the maximum number:

def getNumbers(maxn=40):
L = range(1, maxn+1)
random.shuffle(L)
return L[0:5]

This recreates the master list as needed. If you are running a lot of
trials, or if your maxn is huge (in the tens of millions perhaps) you
might want to optimize by re-using the list each time:

all_numbers = range(1, maxn+1)

def getNumbers(L):
random.shuffle(L)
return L[0:5]

getNumbers(all_numbers)

Notice that this has the side-effect of shuffling your master list. In
general, side-effects are to be avoided whenever possible, although this
one is fairly benign.

However, and this is important, consider this note from the random.shuffle
__doc__ string:

Note that for even rather small len(x), the total number of
permutations of x is larger than the period of most random number
generators; this implies that "most" permutations of a long
sequence can never be generated.

In other words, this is not a good technique for producing a fair lottery.
The results will be biased.

Here is a method that is less biased:

def genNumbers(maxn=40):
# Warning: untested
L = range(1, maxn+1)
chosen = []
for _ in range(5):
n = random.choice(L)
L.remove(n)
chosen.append(n)
return chosen

If you want to allow duplicates, you can simplify the code:

def genNumbersWithDuplicates(maxn=40):
# Warning: untested
L = range(1, maxn+1)
chosen = []
for _ in range(5):
chosen.append(random.choice(L))
return chosen


Hope this helps.
 
P

Paul Rubin

Steven D'Aprano said:
Generally, lottery numbers are selected from a range, e.g. one to forty.
So rather than passing a global list, just pass the maximum number:

def getNumbers(maxn=40):
L = range(1, maxn+1)
random.shuffle(L)
return L[0:5]

Why is everyone using shuffle?

Python 2.3.4 (#1, Feb 2 2005, 12:11:53)
[GCC 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)] on linux2
Type "help", "copyright", "credits" or "license" for more information. [24, 15, 9, 39, 19]
 
D

Duncan Smith

John said:
I'm working on another exercise now about generating random numbers for
the lottery. What I want to do is write a function that picks 5 random
numbers from 1-53 and returns them. Here's what I have so far:

numbers = range(1, 54)

def genNumbers():
for x in range(5):
fiveNumbers = []
number = random.choice(numbers)
numbers.remove(number)
fiveNumbers = fiveNumbers.append(number)
return fiveNumbers

Other than being sort of ugly, this also has the side effect of actually
editing the original list, which I don't want since I will want to
generate more than one set of numbers.

Is there a better way to extract a certain number of items from a list
(so maybe I don't need the for loop)? Is a list even the right type to
use, since it gets edited in place? Perhaps a set?


Another approach (of possibly academic interest, if that).
rand = random.randint
numbers = range(1, n+1)
for i in xrange(draws):
for j in range(k):
rand_int = rand(j, n-1)
numbers[j], numbers[rand_int] = numbers[rand_int], numbers[j]
yield numbers[:k]

n = genNumbers(5, 54, 3)
list(n) [[23, 11, 9, 53, 45], [54, 29, 36, 22, 46], [20, 8, 29, 43, 12]]

Duncan
 
B

bruno at modulix

John said:
I'm working on another exercise now about generating random numbers for
the lottery. What I want to do is write a function that picks 5 random
numbers from 1-53 and returns them. Here's what I have so far:

numbers = range(1, 54)

def genNumbers():
for x in range(5):
fiveNumbers = []

You rebind fiveNumbers on each iteration - so the results of the
previous iteration are lost.
number = random.choice(numbers)
numbers.remove(number)

ok, you already know the problem with this line
fiveNumbers = fiveNumbers.append(number)

list.append() returns None. Guess what will be the value of fiveNumbers
after this statement ?
return fiveNumbers

And you exit the function at the first iteration.
Other than being sort of ugly, this also has the side effect of actually
editing the original list,

This is not the only problem here !-)
which I don't want since I will want to
generate more than one set of numbers.

Is there a better way to extract a certain number of items from a list
(so maybe I don't need the for loop)? Is a list even the right type to
use, since it gets edited in place? Perhaps a set?

Your code is wrong, but still close to the simplest solution. Using
random.choice() and removing used items from the list to choose from
seems quite ok to me. Now shat you need to do is:
1/ create a new list to choose from each time the function is called
2/ create the results list *before* the loop
3/ return *after* the loop

The simplest fix:

def genNumbers():
choose_from = range(1, 54)
results = []
for x in range(5):
number = random.choice(choose_from)
choose_from.remove(number)
results.append(number)
# end for
return results
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,291
Messages
2,571,453
Members
48,137
Latest member
IndiraMcCo

Latest Threads

Top