Drawing without repeats, and seeds
A teacher picks three students to present their projects today, and nobody should present twice. Picking with choice three times looks like it should work, but nothing stops it from picking the same student again:
import random
random.seed(3)
students = ["Ada", "Ben", "Cy", "Di", "Eve", "Finn"]
for i in range(3):
print(random.choice(students))
Ben
Eve
EveEve has to present twice. Every call of choice is independent: it doesn't know which students it picked before, so each pick can land on anyone. The same goes for randint.
sample
What the teacher needs is like drawing names out of a hat: a name that has been drawn isn't in the hat anymore. That's what random.sample(items, count) does. It picks count different items of items and returns them in a new list:
import random
random.seed(7)
students = ["Ada", "Ben", "Cy", "Di", "Eve", "Finn"]
presenters = random.sample(students, 3)
print("Presenting today:", presenters)
print("The class:", students)
Presenting today: ['Cy', 'Ben', 'Di']
The class: ['Ada', 'Ben', 'Cy', 'Di', 'Eve', 'Finn']Unlike shuffle, sample leaves the list it was given as it was. It never picks the same item of the list twice, so if the list has no repeated values, neither does the sample.
The items can be a range too. A puzzle game hides 4 mines on a board whose squares are numbered 1 to 25, and two mines can't share a square:
import random
random.seed(7)
mines = random.sample(range(1, 26), 4)
print("Mines on squares:", sorted(mines))
Mines on squares: [5, 11, 13, 21]The sample comes in the order it was drawn; sorted only makes it easier to read. There must be enough items to pick from. Asking for 5 different students out of 3 is an error, and the error message calls the items the population:
import random
students = ["Ada", "Ben", "Cy"]
print(random.sample(students, 5))
Traceback (most recent call last):
File "main.py", line 4, in <module>
print(random.sample(students, 5))
~~~~~~~~~~~~~^^^^^^^^^^^^^
ValueError: Sample larger than population or is negativeWhere random numbers come from
A computer follows its instructions exactly, so it can't really roll a die. Instead, random calculates its numbers with a formula: each new number comes from the state the previous one left behind. The value the whole series starts from is called the seed. The same seed always gives the same series of numbers:
>>> import random
>>> random.seed(42)
>>> random.randint(1, 100)
82
>>> random.randint(1, 100)
15
>>> random.seed(42)
>>> random.randint(1, 100)
82When a program doesn't set a seed, Python picks one from something that changes all the time, which is why every run is different. Numbers made like this are called pseudorandom: they look random and are good enough for games and simulations, but whoever knows the seed can repeat them. (For passwords and other secrets, Python has the secrets module.)
A fixed seed is useful in its own right. A program that misbehaves only now and then can be made to misbehave on every run, so you can find the bug. The checks on this site use seeds the same way to test functions that use random numbers.