Intro to Random Number Generation in Python

Amir Edris
9 min readAug 25, 2020

I think that a cool thing to learn as a beginner in python is how to produce random numbers. Random number generation is an interesting topic to get into learn simply because of its applications. This post will go over surface-level concepts of random number generation and the different ways we can go about using them with examples.

Computers are deterministic, meaning that if you put an input you will get the same output no matter how many times you try. This is because computers take inputs then follow sets of instructions the give you your output. So how do you get a computer to make random numbers? Computers generate something that is called a pseudo-random number or a number that emulates a random number but is produced predictably from algorithms and an input number or as they are called in this case a “Seed number.”

An early example of a pseudo-random number generation algorithm was known as the middle square method. The middle square method would start with any number, square it and remove the middle digits and use that sequence as your random number string. We have come a long way from algorithms like these used in the mid 20th century. What you really need to know is that these algorithms take in a seed and from that, generate a random number table which can then be used for simulations

What is a seed? If you have ever played Minecraft, you have experience using seeds even if you weren’t aware of it. In Minecraft, random worlds are generated using an input seed that users can put in themselves to make their “randomly generated world” reproducible. Let’s say you play and randomly made a world that you want other people to be able to produce precisely the same all you would have to do is provide the sequence of numbers used to create that world. In programming, if you make a script and want the results to be reproducible while also needing random numbers, you would specify what seed you are using at the start of the program so that the random numbers used are going to be the same every time.

Random number generators, sets of algorithms made to produce pseudo-random numbers, are deterministic. That means that random number generators will always give you the same output given the input seed. The algorithm will take the seed and use it to produce the set of random numbers, so if the seed is the same, the output will always be the same.

Now that we have a little understanding of how random number generation works, let’s go over the ways to use them in python. Python has a built in module called random which, believe it or not, is used for random numbers.

Python uses a Mersenne Twister Core Generator implemented in the c language for efficiency. The Mersenne Twister is one of the most popular algorithms for random number generation currently.

import random
random_number = random.random()
print(random_number)

random.randint(low, high) will give you an integer, or whole number, between the “low” and “high” that you give it.

import random
random_int = random.randint(1,10)
print(random_int)

random.randrange(start, stop, step) will give you a random number within the range(start, stop) that you give it. The step portion lets it know what numbers to look at, for example, random.randrange(1, 20, 3) will look at every three digits in the range 1–20 and give you one of those at random.

import random
random_number = random.randrange(1,20,3)
print(random_number)

random.choice(sequence) will make a selection from a list that you pass in at random

import random
names = ['Steph','Klay','Kyrie','LeBron']
random.choice(names)

There are plenty more in the python docs that you can look at:https://docs.python.org/3/library/random.html.

Since we got that out the way, let’s use the python random module to make a coinflip function. We want to be able to pass times as the argument and get x coinflips. If any of the explanations are confusing, id, recommend looking over the code and playing around with it.

code:https://github.com/AmirMEdris/Blog/blob/master/Examples%20of%20different%20random%20functions.ipynb

import random
def seed_warning(seed):
if seed == None:
print('WARNING:there is no seed a random one will be used')
else:
print('using seed {}'.format(seed))
def coinflip(times): # Declare function name and arguement
for flip in range(0,times):
coin = random.random()
if coin < 0.5:
print('Flip #',flip+1,': Tails')
else:
print('Flip #',flip+1,': Heads')
coinflip(5)

We made two functions here. You can ignore seed_warning for now. Running coinflip(5) should give an output that looks like this.

Flip # 1 : Tails
Flip # 2 : Heads
Flip # 3 : Heads
Flip # 4 : Tails
Flip # 5 : Tails

Now we can use the random.seed() function with our coinflip function so we have consistent results. The seed warning function we made will tell us at the beginning of our function that we either did or didn’t use a seed.

def coinflip(times,seed=None):
seed_warning(seed)
random.seed(seed)
for flip in range(0,times):
coin = random.random()
if coin < 0.5:
print('Flip #',flip+1,': Tails')
else:
print('Flip #',flip+1,': Heads')

In our functions arguments, we added ‘seed’ = None, which means that when we use coinflip if we only specify times and not seed, the default seed is None. Now let’s run this.

coinflip(5,seed = 1)

Set the seed to 1, and we should both get

using seed 1
Flip # 1 : Tails
Flip # 2 : Heads
Flip # 3 : Heads
Flip # 4 : Tails
Flip # 5 : Tails

the first line came from our seed warning function. If you also used seed 1 and flipped 5 or more coins, our first five results should be the same here.

Now let’s use random.seed to make every coinflip the same

def unfaircoinflip(times,seed=None):
seed_warning(seed)
for flip in range(0,times):
random.seed(seed)
coin = random.random()
if coin < 0.5:
print('Flip #',flip+1,': Tails')
else:
print('Flip #',flip+1,': Heads')
unfaircoinflip(6,seed=1)

and run it.

using seed 1 
Flip # 1 : Tails
Flip # 2 : Tails
Flip # 3 : Tails
Flip # 4 : Tails
Flip # 5 : Tails
Flip # 6 : Tails

So what changed? The two functions we made are nearly identical but in the second one, we are setting the seed in the first line of the loop so every iteration resets the random number generator so every answer will be equal to the first answer.

Finally, let’s use the random.choice() method to make a sequence of DNA.

def random_gene(length, seed = None):
seed_warning(seed)
random.seed(seed)
Bases = 'TCAG'
seq = ''
for bases in range(0,length):
choice = random.choice(Bases)
seq+= choice
print(seq)
random_gene(50, seed =1)

The overall structure is that we first declare our string of bases, “Bases”, then we make an empty string seq. For the number of values that we need in our sequence, we make a random choice from bases and add them to our sequence.

Running that should look like

using seed 1 
CTATGGGGCTGTGGTGACTATTTTGCGTCGGCACCGATGTCATAGCAAGG

……………………………………..

np.random.rand(arrays, array_length), makes an n-dimensional array of random floats between 0 and 1. what that means is that it will give us multiple arrays, of length array_length, with random values between 0 and 1 in each slot. Remaking our coinflip using np.random.rand() looks like:

def NpFlip(trials, amount,seed = None):
seed_warning(seed)
np.random.seed(seed)
random_numbers = np.random.rand(trials, amount)
coinflips=[]
for trial in random_numbers:
trial_results = []
for flip in trial:
if flip < 0.5:
trial_results.append('Tails')
else:
trial_results.append('Heads')
coinflips.append(trial_results)
print(coinflips)
NpFlip(2, 6)

since we are now going to get multiple arrays back instead of one value at a time, when we generate our random numbers, we can go through each array using a for loop and go through each item within that array with a nested for loop and for every value add either heads or tails to a list and print it in the end.

WARNING:there is no seed a random one will be used
[['Tails', 'Tails', 'Tails', 'Heads', 'Heads', 'Heads'], ['Tails', 'Tails', 'Heads', 'Tails', 'Tails', 'Heads']]

Ok, so numpy may have complicated our coinflip function a bit, but it depends on how and when you use it. For our gene function, numpy makes it look much cleaner.

def np_short_random_gene(length,seed = None):
seed_warning(seed)
np.random.seed(seed)
Bases = ['T','C','A','G']
seq = np.random.choice(Bases,length,replace = True)
print(''.join(seq))

np.random.choice(seq, choices) makes a random choice on the sequence you give it for however many options you ask for. The replace argument will make it so that the result list can have duplicates if true.

Just to conceptualize, you can use the few things here you just learned to make a pokemon style fight with random choices operating as the ai. I’m gonna try to make every part of it into a function so that its easier to understand all the moving parts.

The first thing we’ll do is make a function that will roll 1–100 for both players and find out who goes first

def goes_first(namea,nameb):
aroll = random.randint(1,100)
broll = random.randint(1,100)
if aroll <= broll:
first = nameb
else:
first = namea
return first

Next, we are gonna make a function that will randomly return 4 moves from a list of pokemon moves. We are gonna use this to keep the games fresh and randomly assign each player 4 moves.

def player_moves():
pokemon_moves = ['absorb', 'volt-tackle', 'accelerock',
'acid', 'acrobatics', 'watergun', 'squirt',
'tackle', 'cut','splash', 'shock', 'hyper-beam']
move_list = np.random.choice(pokemon_moves,4)
return move_list

We’ll need a function that takes player moves and assigns them random values for damage.

def player_stats(moves):
moves_stats = []
for move in moves:
move_stat = random.randint(1,20)
moves_stats.append((move,move_stat))
return moves_stats

For simplicity, we are gonna make a function that does the last two functions together.

def player_setup():
moves = player_moves()
moves = player_stats(moves)
return moves

Now we are going to make two functions, one for the ais move and one for the player’s move. The ai’s move is just going to be a random selection from the 4 moves it has using random.choice(). The player is going to have his options printed to the console and be able to read his abilities then pick one to use.

def ai_move(move_stats):
move = random.choice(move_stats)
return move
def player_move(move_stats):
x = 0
print('_____PLAYER ATTACK_____')
for i in move_stats:
x+=1
print('move#{} You have {} which does around {} damage'.format(x,i[0],i[1]))
print('-----------------------------------------')
move_key = int(input('Type the move number of the move youd like to use:'))
move = move_stats[move_key-1]
print('-----------------------------------------')
return move

Now that we have ways for both the player and ai to express what they want to do we need to make a function that actually carries out their attacks.

def player_turn(player1,player2,moves,enemy_hp):
attack = player_move(moves)
enemy_hp -= attack[1]
print("{} used {} it did {} damage to {}".format(player1, attack[0],
attack[1],player2))
return enemy_hp
def ai_turn(player1,player2,moves,enemy_hp):
print('______ENEMY_ATTACK_____')
attack = ai_move(moves)
enemy_hp -= attack[1]
print("{} used {} it did {} damage to {}".format(player1, attack[0],
attack[1],player2))
return enemy_hp

the above functions just take in the names of player1 and player2, the set of moves that the attacking player has, and the enemies hp. It subtracts the damage from the attack and prints what happened to the user.

Almost done, we need a function to let us know what turn we are on and who is going first.

def print_turn(turn,first):
print('-----------------------------------------')
print('Turn #',turn)
print('-----------------------------------------')
if turn == 1:
print('first is {}'.format(first))

In our game, we want whoever’s first to attack then print the damage done then the second player attacks and print the damage done then print hp. We are going to make two functions that do just that for both cases of if the player goes first and if the ai goes first.

def player_first_move(first,second,first_moves,second_moves,ai_hp,    player_hp):
ai_hp = player_turn(first,second,first_moves,ai_hp)
player_hp = ai_turn(second,first,second_moves,player_hp)
print('You have {} hp, Your opponent has {} hp'.format(player_hp,ai_hp))
return ai_hp, player_hp
def ai_first_move(first,second,first_moves,second_moves,ai_hp,player_hp):
player_hp = ai_turn(second,first,second_moves,player_hp)
ai_hp = player_turn(first,second,first_moves,ai_hp)
print('You have {} hp, Your opponent has {} hp'.format(player_hp,ai_hp))
return ai_hp, player_hp

Now one last function to check who won

def win_check(player_hp):
if player_hp <=0:
print('you lost')
else:
print('you won')

And we can finally put it all together

def pokemon_fight(a,b,seed = None):

seed_warning(seed)
player_hp = 100
ai_hp = 100
first = goes_first(a,b)
second = b if first == a else a
first_moves = player_setup()
second_moves = player_setup()
turn = 1

while player_hp > 0 and ai_hp > 0:
print_turn(turn,first)
if first == a:
ai_hp,player_hp = player_first_move(first,second,first_moves,second_moves,ai_hp,player_hp)
else:
ai_hp,player_hp = ai_first_move(first,second,first_moves,second_moves,ai_hp,player_hp)
turn+=1

win_check(player_hp)

We now have a function pokemon_fight(player1,player2,seed=x) that will run our game.

As mentioned before playing around with the code: https://github.com/AmirMEdris/Blog/blob/master/Examples%20of%20different%20random%20functions.ipynb. will probably be helpful if you my explanations didn’t make sense to you.

I hope this was helpful to someone thank you for reading

--

--

Amir Edris

Data Scientist/ Machine learning engineer who loves to share about this fascinating field