Genetic Algorithm - creatures in 2d world are not learning - python

Goal -
I am trying to implement a genetic algorithm to optimise the fitness of a
species of creatures in a simulated two-dimensional world. The world contains edible foods, placed at random, and a population of monsters (your basic zombies). I need the algorithm to find behaviours that keep the creatures well fed and not dead.
What i have done -
So i start off by generating a 11x9 2d array in numpy, this is filled with random floats between 0 and 1. I then use np.matmul to go through each row of the array and multiply all of the random weights by all of the percepts (w1+p1*w2+p2....w9+p9) = a1.
This first generation is run and I then evaluate the fitness of each creature using (energy + (time of death * 100)). From this I build a list of creatures who performed above the average fitness. I then take the best of these "elite" creatures and put them back into the next population. For the remaining space I use a crossover function which takes two randomly selected "elite" creatures and mixes their genes. I have tested two different crossover functions one which does a two point crossover on each row and one which takes a row from each parent until the new child has a complete chromosome. My issue is that the creatures just don't really seem to be learning, at 75 turns I will only get 1 survivor every so often.
I am fully aware this might not be enough to go off but I am truly stuck on this and cannot figure out how to get these creatures to learn even though I think I am implementing the correct procedures. Occasionally I will get a 3-4 survivors rather than 1 or 2 but it appears to occur completely randomly, doesn't seem like there is much learning happening.
Below is the main section of code, it includes everything I have done but none of the provided code for the simulation
#!/usr/bin/env python
from cosc343world import Creature, World
import numpy as np
import time
import matplotlib.pyplot as plt
import random
import itertools
# You can change this number to specify how many generations creatures are going to evolve over.
numGenerations = 2000
# You can change this number to specify how many turns there are in the simulation of the world for a given generation.
numTurns = 75
# You can change this number to change the world type. You have two choices - world 1 or 2 (described in
# the assignment 2 pdf document).
worldType=2
# You can change this number to modify the world size.
gridSize=24
# You can set this mode to True to have the same initial conditions for each simulation in each generation - good
# for development, when you want to have some determinism in how the world runs from generation to generation.
repeatableMode=False
# This is a class implementing you creature a.k.a MyCreature. It extends the basic Creature, which provides the
# basic functionality of the creature for the world simulation. Your job is to implement the AgentFunction
# that controls creature's behaviour by producing actions in response to percepts.
class MyCreature(Creature):
# Initialisation function. This is where your creature
# should be initialised with a chromosome in a random state. You need to decide the format of your
# chromosome and the model that it's going to parametrise.
#
# Input: numPercepts - the size of the percepts list that the creature will receive in each turn
# numActions - the size of the actions list that the creature must create on each turn
def __init__(self, numPercepts, numActions):
# Place your initialisation code here. Ideally this should set up the creature's chromosome
# and set it to some random state.
#self.chromosome = np.random.uniform(0, 10, size=numActions)
self.chromosome = np.random.rand(11,9)
self.fitness = 0
#print(self.chromosome[1][1].size)
# Do not remove this line at the end - it calls the constructors of the parent class.
Creature.__init__(self)
# This is the implementation of the agent function, which will be invoked on every turn of the simulation,
# giving your creature a chance to perform an action. You need to implement a model here that takes its parameters
# from the chromosome and produces a set of actions from the provided percepts.
#
# Input: percepts - a list of percepts
# numAction - the size of the actions list that needs to be returned
def AgentFunction(self, percepts, numActions):
# At the moment the percepts are ignored and the actions is a list of random numbers. You need to
# replace this with some model that maps percepts to actions. The model
# should be parametrised by the chromosome.
#actions = np.random.uniform(0, 0, size=numActions)
actions = np.matmul(self.chromosome, percepts)
return actions.tolist()
# This function is called after every simulation, passing a list of the old population of creatures, whose fitness
# you need to evaluate and whose chromosomes you can use to create new creatures.
#
# Input: old_population - list of objects of MyCreature type that participated in the last simulation. You
# can query the state of the creatures by using some built-in methods as well as any methods
# you decide to add to MyCreature class. The length of the list is the size of
# the population. You need to generate a new population of the same size. Creatures from
# old population can be used in the new population - simulation will reset them to their
# starting state (not dead, new health, etc.).
#
# Returns: a list of MyCreature objects of the same length as the old_population.
def selection(old_population, fitnessScore):
elite_creatures = []
for individual in old_population:
if individual.fitness > fitnessScore:
elite_creatures.append(individual)
elite_creatures.sort(key=lambda x: x.fitness, reverse=True)
return elite_creatures
def crossOver(creature1, creature2):
child1 = MyCreature(11, 9)
child2 = MyCreature(11, 9)
child1_chromosome = []
child2_chromosome = []
#print("parent1", creature1.chromosome)
#print("parent2", creature2.chromosome)
for row in range(11):
chromosome1 = creature1.chromosome[row]
chromosome2 = creature2.chromosome[row]
index1 = random.randint(1, 9 - 2)
index2 = random.randint(1, 9 - 2)
if index2 >= index1:
index2 += 1
else: # Swap the two cx points
index1, index2 = index2, index1
child1_chromosome.append(np.concatenate([chromosome1[:index1],chromosome2[index1:index2],chromosome1[index2:]]))
child2_chromosome.append(np.concatenate([chromosome2[:index1],chromosome1[index1:index2],chromosome2[index2:]]))
child1.chromosome = child1_chromosome
child2.chromosome = child2_chromosome
#print("child1", child1_chromosome)
return(child1, child2)
def crossOverRows(creature1, creature2):
child = MyCreature(11, 9)
child_chromosome = np.empty([11,9])
i = 0
while i < 11:
if i != 10:
child_chromosome[i] = creature1.chromosome[i]
child_chromosome[i+1] = creature2.chromosome[i+1]
else:
child_chromosome[i] = creature1.chromosome[i]
i += 2
child.chromosome = child_chromosome
return child
# print("parent1", creature1.chromosome[:3])
# print("parent2", creature2.chromosome[:3])
# print("crossover rows ", child_chromosome[:3])
def newPopulation(old_population):
global numTurns
nSurvivors = 0
avgLifeTime = 0
fitnessScore = 0
fitnessScores = []
# For each individual you can extract the following information left over
# from the evaluation. This will allow you to figure out how well an individual did in the
# simulation of the world: whether the creature is dead or not, how much
# energy did the creature have a the end of simulation (0 if dead), the tick number
# indicating the time of creature's death (if dead). You should use this information to build
# a fitness function that scores how the individual did in the simulation.
for individual in old_population:
# You can read the creature's energy at the end of the simulation - it will be 0 if creature is dead.
energy = individual.getEnergy()
# This method tells you if the creature died during the simulation
dead = individual.isDead()
# If the creature is dead, you can get its time of death (in units of turns)
if dead:
timeOfDeath = individual.timeOfDeath()
avgLifeTime += timeOfDeath
else:
nSurvivors += 1
avgLifeTime += numTurns
if individual.isDead() == False:
timeOfDeath = numTurns
individual.fitness = energy + (timeOfDeath * 100)
fitnessScores.append(individual.fitness)
fitnessScore += individual.fitness
#print("fitnessscore", individual.fitness, "energy", energy, "time of death", timeOfDeath, "is dead", individual.isDead())
fitnessScore = fitnessScore / len(old_population)
eliteCreatures = selection(old_population, fitnessScore)
print(len(eliteCreatures))
newSet = []
for i in range(int(len(eliteCreatures)/2)):
if eliteCreatures[i].isDead() == False:
newSet.append(eliteCreatures[i])
print(len(newSet), " elites added to pop")
remainingRequired = w.maxNumCreatures() - len(newSet)
i = 1
while i in range(int(remainingRequired)):
newSet.append(crossOver(eliteCreatures[i], eliteCreatures[i-1])[0])
if i >= (len(eliteCreatures)-2):
i = 1
i += 1
remainingRequired = w.maxNumCreatures() - len(newSet)
# Here are some statistics, which you may or may not find useful
avgLifeTime = float(avgLifeTime)/float(len(population))
print("Simulation stats:")
print(" Survivors : %d out of %d" % (nSurvivors, len(population)))
print(" Average Fitness Score :", fitnessScore)
print(" Avg life time: %.1f turns" % avgLifeTime)
# The information gathered above should allow you to build a fitness function that evaluates fitness of
# every creature. You should show the average fitness, but also use the fitness for selecting parents and
# spawning then new creatures.
# Based on the fitness you should select individuals for reproduction and create a
# new population. At the moment this is not done, and the same population with the same number
# of individuals is returned for the next generation.
new_population = newSet
return new_population
# Pygame window sometime doesn't spawn unless Matplotlib figure is not created, so best to keep the following two
# calls here. You might also want to use matplotlib for plotting average fitness over generations.
plt.close('all')
fh=plt.figure()
# Create the world. The worldType specifies the type of world to use (there are two types to chose from);
# gridSize specifies the size of the world, repeatable parameter allows you to run the simulation in exactly same way.
w = World(worldType=worldType, gridSize=gridSize, repeatable=repeatableMode)
#Get the number of creatures in the world
numCreatures = w.maxNumCreatures()
#Get the number of creature percepts
numCreaturePercepts = w.numCreaturePercepts()
#Get the number of creature actions
numCreatureActions = w.numCreatureActions()
# Create a list of initial creatures - instantiations of the MyCreature class that you implemented
population = list()
for i in range(numCreatures):
c = MyCreature(numCreaturePercepts, numCreatureActions)
population.append(c)
# Pass the first population to the world simulator
w.setNextGeneration(population)
# Runs the simulation to evaluate the first population
w.evaluate(numTurns)
# Show the visualisation of the initial creature behaviour (you can change the speed of the animation to 'slow',
# 'normal' or 'fast')
w.show_simulation(titleStr='Initial population', speed='normal')
for i in range(numGenerations):
print("\nGeneration %d:" % (i+1))
# Create a new population from the old one
population = newPopulation(population)
# Pass the new population to the world simulator
w.setNextGeneration(population)
# Run the simulation again to evaluate the next population
w.evaluate(numTurns)
# Show the visualisation of the final generation (you can change the speed of the animation to 'slow', 'normal' or
# 'fast')
if i==numGenerations-1:
w.show_simulation(titleStr='Final population', speed='normal')

Related

Trying to add a progress bar as my python program runs

I am a beginner writing a Python code, where the computer generates a random number between 1 and 10, 1 and 100, 1 and 1000, 1 and 10000, 1 and 100000 and so on. The computer itself will guess the random number a number of times (a user input number), and every time there is a count of how many times the computer took to guess the random number. A mean of the count over the number of times will be calculated and put in an array, where matplotlib will generate a graph of x=log10(the upper bounds of the random number range, i.e. 10, 100, 1000,...)
At the moment, I print the log10 of each bound as it is processed, and that has been acting as my progress tracker. But I am thinking of adding my progress bar, and I don't know where to put it so that I can see how much of the overall program has run.
I have added tqdm.tqdm in all sorts of different places to no avail. I am expecting a progress bar increasing as the program runs.
My program is as shown.
# Importing the modules needed
import random
import time
import timeit
import numpy as np
import matplotlib.pyplot as plt
import tqdm
# Function for making the computer guess the number it itself has generated and seeing how many times it takes for it to guess the number
def computer_guess(x):
# Telling program that value "low" exists and it's 0
low = 0
# Telling program that value "high" exists and it's the arbitrary parameter x
high = x
# Storing random number with lower limit "low" and upper limit "high" as "ranno" for the while-loop later
ranno = random.randint(low, high)
# Setting initial value of "guess" for iteration
guess = -1
# Setting initial value of "count" for iteration
count = 1
# While-loop for all guessing conditions
while guess != ranno:
# Condition: As long as values of "low" and "high" aren't the same, keep guessing until the values are the same, in which case "guess" is same as "low" (or "high" becuase they are the same anyway)
if low != high:
guess = random.randint(low, high)
else:
guess = low
# Condition: If the guess if bigger than the random number, lower the "high" value to one less than 1, and add 1 to the guess count
if guess > ranno:
high = guess - 1
count += 1
# Condition: If the guess if smaller than the random number, increase the "low" value to one more than 1, and add 1 to the guess count
elif guess < ranno:
low = guess + 1
count += 1
# Condition: If it's not either of the above, i.e. the computer has guessed the number, return the guess count for this function
else:
return count
# Setting up a global array "upper_bounds" of the range of range of random numbers as a log array from 10^1 to 10^50
upper_bounds = np.logspace(1, 50, 50, 10)
def guess_avg(no_of_guesses):
# Empty array for all averages
list_of_averages = []
# For every value in the "upper_bounds" array,
for bound in upper_bounds:
# choose random number, "ranx", between 0 and the bound in the array
ranx = random.randint(0, bound)
# make an empty Numpy array, "guess_array", to insert the guesses into
guess_array = np.array([])
# For every value in whatever the no_of_guesses is when function called,
for i in np.arange(no_of_guesses):
# assign value, "guess", as calling function with value "ranx"
guess = computer_guess(ranx)
# stuff each resultant guess into the "guess_array" array
guess_array = np.append(guess_array, guess)
# Print log10 of each value in "upper_bound"
print(int(np.log10(bound)))
# Finding the mean of each value of the array of all guesses for the order of magnitude
average_of_guesses = np.mean(guess_array)
# Stuff the averages of guesses into the array the empty array made before
list_of_averages.append(average_of_guesses)
# Save the average of all averages in the list of averages into a single variable
average_of_averages = np.mean(list_of_averages)
# Print the list of averages
print(f"Your list of averages: {list_of_averages}")
# Print the average of averages
print(f"Average of averages: {average_of_averages}")
return list_of_averages
# Repeat the "guess_avg" function as long as the program is running
while True:
# Ask user to input a number for how many guesses they want the computer to make for each order of magnitude, and use that number for calling the function "guess_avg()"
resultant_average_numbers = guess_avg(
int(input("Input the number of guesses you want the computer to make: ")))
# Plot a graph with log10 of the order of magnitudes on the horizontal and the returned number of average of guesses
plt.plot(np.log10(upper_bounds), resultant_average_numbers)
# Show plot
plt.show()
I apologise if this is badly explained, it's my first time using Stackoverflow.
You can define the following progress_bar function, which you will call from wherever you want to monitor the advancement in you code:
import colorama
def progress_bar(progress, total, color=colorama.Fore.YELLOW):
percent = 100 * (progress / float(total))
bar = '█' * int(percent) + '-' * (100 - int(percent))
print(color + f'\r|{bar}| {percent:.2f}%', end='\r')
if progress == total:
print(colorama.Fore.GREEN + f'\r|{bar}| {percent:.2f}%', end='\r')
Hope this helps
You can also call tqdm by hand and then update it manually.
progress_bar = tqdm.tqdm(total=100)
progress_bar.update()
When you are finished, you can call progress_bar.clear() to start again.
You probably want two progress bars in the guess_avg() function. One to track the ranges and another to track the guesses.
In this example I've used the Enlighten progress bar library, but you can accomplish similar behavior with other libraries that support nested progress bars. One advantage Enlighten is going to have over others is you can print whatever you want while the progress bar is running, good for debugging.
You can make this simpler by using context managers and auto-updating counters, but I didn't do that here to make it clearer what's happening. You can also customize the template used for the progress bar.
import enlighten
def guess_avg(no_of_guesses):
# Empty array for all averages
list_of_averages = []
# For every value in the "upper_bounds" array,
# Create a progress bar manager manager
manager = enlighten.get_manager(leave=False)
# Create main progress bar for ranges
pbar_bounds = manager.counter(total=len(upper_bounds), desc='Bound ranges', unit='ranges')
for bound in upper_bounds:
# choose random number, "ranx", between 0 and the bound in the array
ranx = random.randint(0, bound)
# make an empty Numpy array, "guess_array", to insert the guesses into
guess_array = np.array([])
# For every value in whatever the no_of_guesses is when function called,
# Create nested progress bar for guesses, leave removes progress bar on close
pbar_guesses = manager.counter(total=no_of_guesses, desc='Guessing', unit='guesses', leave=False)
for i in np.arange(no_of_guesses):
# assign value, "guess", as calling function with value "ranx"
guess = computer_guess(ranx)
# stuff each resultant guess into the "guess_array" array
guess_array = np.append(guess_array, guess)
pbar_guesses.update() # Update nested progress bar
pbar_guesses.close() # Close nested progress bar
# Print log10 of each value in "upper_bound"
print(int(np.log10(bound))) # You can remove this now if you want
pbar_bounds.update() # Update main progress bar
# Finding the mean of each value of the array of all guesses for the order of magnitude
average_of_guesses = np.mean(guess_array)
# Stuff the averages of guesses into the array the empty array made before
list_of_averages.append(average_of_guesses)
manager.stop() # Stop the progress bar manager
# Save the average of all averages in the list of averages into a single variable
average_of_averages = np.mean(list_of_averages)
# Print the list of averages
print(f"Your list of averages: {list_of_averages}")
# Print the average of averages
print(f"Average of averages: {average_of_averages}")
return list_of_averages

Calculating probability of a Fantasy League in Python

Inspired by some projects, I have decided to work on a calculator project based on Python.
Essentially, I have 5 teams in a fantasy league, with points assigned to these teams based on their current standings. Teams A-E.
Assuming the league has 10 more matches to be played, my main aim is to calculate the probability that a team makes it to the top 3 in their league given the matches have a 33.3% of either going:
A win to the team (which adds 2 points to the winning team)
A lose to the team (which awards 0 points to the losing team)
A draw (which awards 1 point to both teams)
This also in turn means there will be 3^10 outcomes for the 10 matches to be played.
For each of these 3^10 scenarios, I will also compute how the final points table will look, and from there, I will be able to sort and figure out which are the top 3 teams in the fantasy league.
I've worked halfway through the project, as shown:
Points = { "A":12, "B":14, "C":8, "D":12, "E":6} #The current standings
RemainingMatches = [
A:B
B:D
C:E
A:E
D:C
B:D
C:D
A:E
C:E
D:C
]
n=len(RemainingMatches) # Number of matches left
combinations = pow(3,n) # Number of possible scenarios left assumes each game has 3 outcomes
print( "Number of remaining matches = %d" % n )
print( "Number of possible scenarios = %d" % combinations )
for i in range(0,combinations)
...
for i in range(0,n)
I am currently wondering how do I get these possible combinations to match a certain scenario? For example, when i = 0, it points to the first matchup where A wins, B losses. Hence, Points[A] = Points[A] + 2 etc. I know there will be a nested loop since I have to consider the other matches too. But how do I exactly map each scenario, and then nest it?
Apologies if I am being unclear here, struggling with this part at the moment.
Thinking Process:
3 outcomes per game.
for i to combinations:
#When i =1, means A win/B lost?
#When i =2, means B win/A lost?
#When i =3, means A/B drew?
for i to n:
#Go to next match?
Not exactly sure what is the logic flow for this kind of scenario. Thank you.
Here is a different way to write the code. If you knew in advance the outcome of each of the ten remaining games, you could compute which teams would finish in top three. This is done with the play_out function, which takes current standings, remaining games, and the known outcomes of future games.
Now all that remains is to loop over all possible future outcomes and tally the winning probabilities. This is done in the simulate_league function that takes in current standings and remaining games, and returns a probability that a given team finishes in top 3.
There may be situations where two teams are tied for the third place. In cases like this, the code below allows for four teams or more to be in "top 3". To use a different rule, you can change the top3scores = nlargest(3, set(pts.values())) line.
In terms of useful Python functions, itertools.product is used to generate all possible win/draw/loss outcomes, and heapq.nlargest is used to find the largest 3 scores out of a bunch. The collections.Counter class is used to count the number of possibilities in which a given team finishes in top 3.
from itertools import product
from heapq import nlargest
from collections import Counter
Points = {"A":12, "B":14, "C":8, "D":12, "E":6} # The current standings
RemainingMatches = ["A:B","B:D","C:E","A:E","D:C","B:D","C:D","A:E","C:E","D:C"]
# reformat remaining matches
RemainingMatches = [tuple(s.split(":")) for s in RemainingMatches]
def play_out(points, remaining_matches, winloss_schedule):
pts = points.copy()
# simulate remaining games given predetermine win/loss/draw outcomes
# given by winloss_schedule
for (team_a, team_b), outcome in zip(remaining_matches, winloss_schedule):
if outcome == -1:
pts[team_a] += 2
elif outcome == 0:
pts[team_a] += 1
pts[team_b] += 1
else:
pts[team_b] += 2
# top 3 scores (allows for ties)
top3scores = nlargest(3, set(pts.values()))
return {team: score in top3scores for team, score in pts.items()}
def simulate_league(points, remaining_matches):
top3counter = Counter()
for winloss_schedule in product([-1, 0, 1], repeat=len(remaining_matches)):
top3counter += play_out(points, remaining_matches, winloss_schedule)
total_possibilities = 3 ** len(remaining_matches)
return {team: top3count / total_possibilities
for team, top3count in top3counter.items()}
# simulate_league(Points, RemainingMatches)
# {'A': 0.9293637487510372,
# 'B': 0.9962573455943369,
# 'C': 0.5461057765584515,
# 'D': 0.975088485833799,
# 'E': 0.15439719554945894}

Numpy arrays incorrectly have identical values?

I have a small program where I am playing with creating an evolutionary algorithm related to disease spread. I have run into an issue that has driven me slightly mad trying to figure out the problem.
I have two numpy arrays, "infections", which is an array where each element is binary representation of whether that individual has been exposed and "current_infections", that is only ongoing infections and is supposed to be incremented by days.
For Example:
infections = [0,0,0,1,1]
current_infections = [0,0,0,15,0]
This would represent five individuals, individual three has had the disease for 15 days and individual four has had it for long enough that they have recovered and no longer currently have it.
infections = []
current_infections = []
responsible_infections = []
spread_rate = 0.1
contagious_period = 20
initial_node_infections = 2
current_network = #this is an array of adjacency matrices for nodes in a network
#initial infections.
def initialize_infections():
global infections, current_infections, responsible_infections
responsible_infections = np.zeros(current_network.shape[0])
infections = np.zeros(current_network.shape[0])
for each in rd.sample(range(len(current_network)), k=initial_node_infections):
infections[each] = 1
current_infections = infections[:]
# runs a day in simulation.
# returns 1 if there are still ongoing infections at the end of day, 0 if not
def progress_day():
global current_infections
print(np.sum(current_infections), np.sum(infections)) #should not be equivalent, yet they are
for i in range(len(current_infections)):
if current_infections[i] >= 1 and current_infections[i]<contagious_period:
current_infections[i]+=1
elif current_infections[i]>=contagious_period:
#patient recovered
current_infections[i] = 0
for i in range(contacts_per_day):
for j in range(len(current_infections)):
if current_infections[j] >= 1:
spread_infection(current_network[j], j)
if not np.sum(current_infections):
return 0
else:
return 1
#given infected node it calculates spread of disease to adjacent nodes.
def spread_infection(person, rp):
global infections, current_infections, responsible_infections
for x in range(len(person)):
if person[x] == 1 and infections[x] == 0 and rd.random()<=spread_rate:
current_infections[x] = 1
infections[x] = 1
responsible_infections[rp]+=1 #infections a given person is responsible for.
def main():
global current_infections, infections
initialize_infections()
day = 0
while day<100:
if not progress_day():
break
day+=1
main()
For some reason changes made to an element in current_infections are also being made to that element in infections so they are both incrementing. Am i doing something incorrectly with numpy such that they are somehow the same array?
current_infections = infections[:] makes current_infections a view over the elements in infections. Use current_infections = infections.copy().

Python programming probability marble out of a bag coding

Hey I'm new to programming but I cant seem to code probability questions. For example, how would I code this?
A box contains 12 transistors of type A and 18 of type B. one transistor is taken out at random and returned. This process is repeated. Determine the probability that the first chosen is type A and second is type B. Thanks!
This is my first try.
from scipy import stats as st
import numpy as np
import random
total=30
totalA=12
totalB=18
def transistor():
return random.choice("A","B")
random.seed(0)
for _in range(30):
try1=transistor()
try2=transistor()
if try1="A":
prob1=totalA/total
else:
prob1=totalB/total
if try2="A":
prob2=totalA/total
else:
prob2=totalB/total
if try1=="A" and try2=="A"
prob=2*totalA/total
If you're trying to run a simulation, this code will give you a probability from 10000 trials. It will generate a different result every time. The more trials, the more accurate it is. The correct, theoretical answer is 0.24.
import random
trials = 10000 # total number of trials
totalA = 12 # total number of A transistors
totalB = 18 # total number of B transistors
successes = 0 # variable keeping track of how many successful pulls there were
choicelist = list("A" * totalA + "B" * totalB) # list containing transitors to correct proportion
def transistor():
return random.choice(choicelist) # pick a random transistor from list
for i in range(trials):
try1 = transistor()
try2 = transistor()
if try1 == "A" and try2 == "B": # if first pull is type A and second is type B...
successes += 1 # ...then it's successful
print float(successes) / trials # print out the proportion of successes to trials

Python Data Structure Selection

Let's say I have a list of soccer players. For now, I only have four players. [Messi, Iniesta, Xavi, Neymar]. More players will be added later on. I want to keep track of the number of times these soccer players pass to each other during the course of a game. To keep track of the passes, I believe I'll need a data structure similar to this
Messi = {Iniesta: 4, Xavi: 5 , Neymar: 8}
Iniesta = {Messi: 4, Xavi: 10 , Neymar: 5}
Xavi = {Messi: 5, Iniesta: 10 , Neymar: 6}
Neymar = {Messi: 8, Iniesta: 5 , Xavi: 6}
Am I right to use a dictionary? If not, what data structure would be better suited? If yes, how do I approach this using a dictionary though? How do I address the issue of new players being included from time to time, and creating a dictionary for them as well.
As an example, If I get the first element in the list, List(i) in the first iteration is Messi, how do i use the value stored in it to create a dictionary with the name Messi. That is how do i get the line below.
Messi = [Iniesta: 4, Xavi: 5 , Neymar: 8]
It was suggested I try something like this
my_dynamic_vars = dict()
string = 'someString'
my_dynamic_vars.update({string: dict()})
Python and programming newbie here. Learning with experience as I go along. Thanks in advance for any help.
This is a fun question, and perhaps a good situation where something like a graph might be useful. You could implement a graph in python by simply using a dictionary whose keys are the names of the players and whose values are lists players that have been passed the ball.
passes = {
'Messi' : ['Iniesta', 'Xavi','Neymar', 'Xavi', 'Xavi'],
'Iniesta' : ['Messi','Xavi', 'Neymar','Messi', 'Xavi'],
'Xavi' : ['Messi','Neymar','Messi','Neymar'],
'Neymar' : ['Iniesta', 'Xavi','Iniesta', 'Xavi'],
}
To get the number of passes by any one player:
len(passes['Messi'])
To add a new pass to a particular player:
passes['Messi'].append('Xavi')
To count the number of times Messi passed to Xavi
passes['Messi'].count('Xavi')
To add a new player, just add him the first time he makes a pass
passes['Pele'] = ['Messi']
Now, he's also ready to have more passes 'appended' to him
passes['Pele'].append['Xavi']
What's great about this graph-like data structure is that not only do you have the number of passes preserved, but you also have information about each pass preserved (from Messi to Iniesta)
And here is a super bare-bones implementation of some functions which capture this behavior (I think a beginner should be able to grasp this stuff, let me know if anything below is a bit too confusing)
passes = {}
def new_pass(player1, player2):
# if p1 has no passes, create a new entry in the dict, else append to existing
if player1 not in passes:
passes[player1] = [player2]
else:
passes[player1].append(player2)
def total_passes(player1):
# if p1 has any passes, return the total number; otherewise return 0
total = len(passes[player1]) if player1 in passes else 0
return total
def total_passes_from_p1_to_p2(player1, player2):
# if p1 has any passes, count number of passes to player 2; otherwise return 0
total = passes[player1].count(player2) if player1 in passes else 0
return total
Ideally, you would be saving passes in some database that you could continuously update, but even without a database, you can add the following code and run it to get the idea:
# add some new passes!
new_pass('Messi', 'Xavi')
new_pass('Xavi', 'Iniesta')
new_pass('Iniesta', 'Messi')
new_pass('Messi', 'Iniesta')
new_pass('Iniesta', 'Messi')
# let's see where we currently stand
print total_passes('Messi')
print total_passes('Iniesta')
print total_passes_from_p1_to_p2('Messi', 'Xavi')
Hopefully you find this helpful; here's some more on python implementation of graphs from the python docs (this was a fun answer to write up, thanks!)
I suggest you construct a two dimensional square array. The array should have dimensions N x N. Each index represents a player. So the value at passes[i][j] is the number of times the player i passed to player j. The value passes[i][i] is always zero because a player can't pass to themselves
Here is an example.
players = ['Charles','Meow','Rebecca']
players = dict( zip(players,range(len(players)) ) )
rplayers = dict(zip(range(len(players)),players.keys()))
passes = []
for i in range(len(players)):
passes.append([ 0 for i in range(len(players))])
def pass_to(f,t):
passes[players[f]][players[t]] += 1
pass_to('Charles','Rebecca')
pass_to('Rebecca','Meow')
pass_to('Charles','Rebecca')
def showPasses():
for i in range(len(players)):
for j in range(len(players)):
print("%s passed to %s %d times" % ( rplayers[i],rplayers[j],passes[i][j],))
showPasses()

Categories

Resources