Python has many useful standard libraries - Today we will look at the random module.
Whether it's generating data to test our code or creating a machine player for a simple game, random numbers end up being rather useful. In this article we'll look at three uses of randomness. Random numbers, randomising order and random choices.
Firstly we need to point out that computers do not generate random numbers! Consider the following example
from random import seed, randint
repeats = 3
for _ in range(repeats):
seed(1) # arbitrary bad seed
for _ in range(20):
print(randint(1, 20), end=", ")
print("")
For me this outputs
>>> 5, 19, 3, 9, 4, 16, 15, 16, 13, 7, 4, 16, 1, 13, 14, 20, 1, 15, 9, 8, 5, 19, 3, 9, 4, 16, 15, 16, 13, 7, 4, 16, 1, 13, 14, 20, 1, 15, 9, 8, 5, 19, 3, 9, 4, 16, 15, 16, 13, 7, 4, 16, 1, 13, 14, 20, 1, 15, 9, 8,
What's important to spot here, is the repetition each time the seed is reset. This is worth bearing in mind if you desire greater randomness than what is essentially a weird sequence. In Python if no seed is specified then an OS-dependent value is taken. In short - bear in mind there are no true random numbers here but they are typically good enough for many cases we come across!
To begin let us generate a random number using random(). This will create a random number between 0 and 1.
from random import random
value = random()
print(f"The value to two decimal places is {value:.2f}"
If you're unsure how the format argument works have a look at my post on formatting. Note how we import the function random from the library random. If we wish to see what the library contains we can use
import random
help(random)
This will output a lot of helpful text! If we only want help on a specific function we can
import random
help(random.random)
This will give us the output
>>> Help on built-in function random: random(...) method of random.Random instance random() -> x in the interval [0, 1).
Let's use a different function from random. For example, let's create a random bearing
from random import uniform
random_bearing = uniform(0, 360)
print(f"{random_bearing:05.1f} Degrees")
This may print out
>>> 004.6 Degrees
Let's say we don't want all numbers, just integers. For that we can use randrange. The arguments are the same as range, so
from random import randrange
randrange(10) # random integer between 0 and 10
randrange(0, 100, 25) # chooses randomly from 0,25,50,75
Another similar function is randint. This includes the end value and does not allow us to perform steps. For example
from random import randint
def sum_of_dice_rolls(n=1, lower=1, upper=6):
"""
Sum of n dice rolls
"""
if n > 0:
return sum([randint(lower, upper) for _ in range(n)])
else:
raise ValueError('Invalid number of dice rolls!')
for n in (1,2,3):
print(f"From {n} rolls you rolled a total of {sum_of_dice_rolls(n)}")
may give
>>> From 1 rolls you rolled a total of 5 From 2 rolls you rolled a total of 10 From 3 rolls you rolled a total of 17
Let's say we want to pick other things randomly, like say names out of a hat. For this we can use the choice function.
from random import choice
names = ('Bob', 'Jerry', 'Mary', 'Francis', 'Jill', 'Barbara')
random_name = choice(names)
print(f"It is {random_name}'s turn!")
Will potentially give
>>> It's Barbara's turn!
If we wish to choose multiple cases we can use
from random import choices
names = ('Bob', 'Jerry', 'Mary', 'Francis', 'Jill', 'Barbara')
for random_name in choices(names, k=6):
print(f"It is {random_name}'s turn!")
this (potentially!) outputs
>>> It is Jerry's turn! It is Bob's turn! It is Mary's turn! It is Jill's turn! It is Jerry's turn! It is Mary's turn!
Here we can see that some names are repeated. This might be okay for our use case but it won't be helpful if we wish to pick everyone just once! Therefore we can use the sample function. This will pick k options as before but won't repeat.
from random import sample
names = ('Bob', 'Jerry', 'Mary', 'Francis', 'Jill', 'Barbara')
for random_name in sample(names, k=6):
print(f"It is {random_name}'s turn!")
For further reading, go to the Random reference page. For the remaining part of this article, we will out list a few example uses of the random library.
One common example is to generate random strings. Here we import four groups of symbols and we will randomly select from these. We will randomly choose a group and then randomly choose a symbol from that group. Here is our code.
from string import ascii_lowercase, ascii_uppercase, punctuation, digits
from random import choice
options = (ascii_lowercase, ascii_uppercase, punctuation, digits)
length = 12
new_password = ''
while len(new_password) < length:
new_password += choice(choice(options))
print(new_password)
will produce something like
>>> 3}z(vvmeO~L3
extension
Turtle is a great little tool for using Python to move a virtual turtle about. Here's a couple of simple programs!
import turtle
from random import choices
dim = 100
turtle.setworldcoordinates(-dim, -dim, dim, dim)
bob = turtle.Turtle()
while True:
x,y = choices(range(-dim, dim), k=2)
bob.goto(x,y)
turtle.exitonclick()
will produce something like
Here we have generated random coordinates for the turtle to move to each step. Another example!
import turtle
from random import random
turtle.speed(10)
prob = 0.5
step = 10
angle = 60
while True:
turtle.forward(step)
if random() > prob:
turtle.left(angle)
else:
turtle.right(angle)
produces
Here we have used random() to ensure that we turn left the same number of times as turning right. In this way we produce a randomised honeycomb pattern.
I'll discuss implementations of this game in further detail in another post, for now let's look at getting input. We need methods of getting player and computer choices for the game. Here's one way to this
from random import choice
moves = ('rock', 'paper', 'scissors')
def computer_choice():
global moves
return choice(moves)
def player_choice():
global moves
while True:
print("Valid moves are", *moves, sep="\n", end="")
player_move = input("Please enter your move >>").lower()
if player_move in moves:
return player_move
else:
print(f"{player_move} is an invalid option, try again!")
Here there a few things to note. Firstly see how we have used the choice function to select a random move for the computer. Secondly we have kept moves outside both functions. This is so if we wish to expand the game later, we only have one variable to change. Finally note that we have written the player_choice() function to prevent an invalid option being presented!
As a final demonstration, I include a crude example of a computer playing batteships. In battleships we want the computer to find all the ships we have hidden.
Ideally we want the computer to visit every tile just once. Furthermore we want the computer to guess adjacent tiles if the computer does in fact get a hit
Here's a gif showing my implementation
Here red squares indicate misses and green indicate hits.
Extensions
import turtle
from random import shuffle, choice, random
dim = 250 # dimension of our screen
L = 25 # size of a box
turtle.setworldcoordinates(-dim, -dim, dim, dim)
def draw_square(tl, x, y, L, fill=False, col='red'):
"""
Draws filled square at x,y location with side of L
if fill is True the box is filled with colour 'col'
"""
tl.penup()
tl.goto(x, y)
tl.pendown()
if fill:
tl.color("black", col)
tl.begin_fill()
for side in range(4):
tl.forward(L)
tl.left(90)
if fill:
tl.end_fill()
tl.penup()
tl.goto(x,y)
bob = turtle.Turtle()
bob.speed(10)
## Generate all possible box locations
locations = []
for x in range(-dim, dim, L):
for y in range(-dim, dim, L):
locations.append((x,y))
shuffle(locations) # Computer will visit all locations randomly
def new_ship(length=3):
"""
Generate location of ships
Code checks if both front and end of randomly placed ship is on screen
if so it returns this
TODO : does not currently check if intersection with existing ships
"""
ship = []
global locations
x,y = choice(locations) # Front of ship!
while len(ship) < length:
step = choice((-1, 1))
if random() > 0.5: # randomly choice horizontal or vertical ship
end_x = x + length * step * L
if (end_x, y) in locations: # Only allow valid end position
for loc in range(x, end_x + 1, step*L):
ship.append((loc, y))
else:
end_y = y + length * step * L
if (x, end_y) in locations:
for loc in range(y, end_y + 1, step*L):
ship.append((x, loc))
return ship
ships = []
for j in range(2, 8):
ships += new_ship(j)
while locations:
x,y = locations.pop(0) # get next guess from front of queue
if (x,y) in ships:
col = 'green'
to_shift = []
for index, (new_x, new_y) in enumerate(locations):
if new_x in range(x-L, x+2*L, L) and new_y in range(y-L, y+2*L, L):
to_shift.append(index)
# Places all adjacent tiles at the front of the queue
for index in to_shift:
locations.insert(0, locations.pop(index))
else:
col = 'red'
draw_square(bob, x, y, L, True, col)
turtle.exitonclick()
Enjoy!