I'm making a program that will go through at least 1,016,064 gear permutations on Diablo 3. I've been experimenting with different theoretical implementations and I decided that I wanted to use classes to represent each permutation rather than having to deal with massive and convoluted dictionaries. With this method I can store an instance and then replace it when a new permutation is superior to the former.
In any case it takes my computer (i7-3632QM) about 40 seconds go through all of the permutations just doing about 30 flops per permutation, and I cant even imagine how long it'll take if it has to define all 50 methods each time a class is instantiated. Anyway, this is what I think it'll look like:
class perm:
def __init__(self, *args):
self.x = 10
self.y = 5
self.z = 100
for item in args:
if hasattr(self, item):
getattr(self, item)()
self.val_1 = self.x * 2
self.val_2 = self.y * 5
self.val_3 = self.z/(self.z+300)
def head_1(self):
self.x += 5
self.z + 200
def head_2(self):
self.x += 10
self.y += 10
def feet_1(self):
self.y += 5
self.z += 250
def feet_2(self):
self.x += 10
self.z += 500
current_best = perm('head_1','feet_2')
It seems like the correct way to do this is to make objects for each of the gear options you have, then a function that calculates them all.
import itertools
class Gear(object):
def __init__(self, *args, **kwargs):
# I have no idea what Gear should do...
class Headpiece(Gear):
...
class Legs(Gear):
...
# etc
def calculate_perm(gear_tuple):
result = do_some_calculation_over(gear_tuple)
return result
best = max(itertools.permutations(all_your_gear), key=calculate_perm)
You could even create one class that's analogous to your perm, though I'd give it a more descriptive name:
class EquipmentSet(object):
slots = ['head', 'legs', ... ]
def __init__(self, head=None, legs=None, ...)
self.head = head
self.legs = legs
...
self.equipment = [getattr(self, item) for item in self.slots]
#property
def x(self)
return sum(item.x for item in self.equipment)
# similar for y and z
#property
def val_1(self):
return self.x * 2
# similar for val_2, val_3
# implement dunder rich comparison methods?
result = max(EquipmentSet(*gearset) for \
gearset in itertools.permutations(all_your_gear))
Strings are just as a example. These lists should contain Gear class, which instances knows what type of 'bonuses' gear gives.
import itertools
headpieces = ['headpiece1', 'headpiece2', 'headpiece3']
armors = ['armor1', 'armor2']
weapons = ['weapon1', 'weapon2']
print list(itertools.product(headpieces, armors, weapons))
# result:
[('headpiece1', 'armor1', 'weapon1'),
('headpiece1', 'armor1', 'weapon2'),
('headpiece1', 'armor2', 'weapon1'),
('headpiece1', 'armor2', 'weapon2'),
('headpiece2', 'armor1', 'weapon1'),
('headpiece2', 'armor1', 'weapon2'),
('headpiece2', 'armor2', 'weapon1'),
('headpiece2', 'armor2', 'weapon2'),
('headpiece3', 'armor1', 'weapon1'),
('headpiece3', 'armor1', 'weapon2'),
('headpiece3', 'armor2', 'weapon1'),
('headpiece3', 'armor2', 'weapon2')]
This code gives you all possible gears in lazy way (without passing it to list() it returns generator), is optimized (itertools are implemented in C) as is elegant. Note that in each element there is only one headpiece / weapon / armor. May be generalized to additional piece of gears.
After that you'll just have to write some kind of aggregator which takes input gear and returns 'score'.
Well I decided to use itertools, a module I have no experience with (but that will change after this!), and I've already half made the script making a test. It works so I might as well finish it even if it isn't the most efficient way, although I'm open to suggestions...
import time, itertools
class Barb:
def __init__(_, args):
_.elements = ['arcane','cold','fire','lightning','poison','physical']
_.strength = 5460 # max ancient str
_.vitality = 140
_.armor = 10188
_.all_res = 250
_.resistances = {element:7.7 for element in _.elements}
_.dodge = 0
_.armor_bonus_percent = .25
_.all_res_bonus_percent = 0
_.life_bonus_percent = .25
_.elemental_damage_reduction = 1
_.regen = 10730
_.life_on_hit = 8035
_.life_per_fury_spent = 0
_.life_percent_per_second_regen = 0
_.damage_mod = 1
_.cc = .05
_.cd = 2.8
_.ias = .25
_.attacks_per_second = 1.69
_.ww_damage_percent = 0
_.dibs = 0
_.cdr = 1
_.elemental_damage_bonus = .2
_.bastions = False
# apply gear bonuses
for arg in args:
getattr(_, arg)()
def helm_1(_):
_.cc += .06
_.ww_damage_percent += .15
_.life_bonus_percent += .23
_.resistances['arcane'] += 210
def helm_2(_):
_.cc += .06
_.vitality += 1000
_.life_bonus_percent += .23
_.resistances['arcane'] += 210
def torso_1(_):
_.vitality += 650
_.life_bonus_percent += .15
_.resistances['fire'] += 210
def torso_2(_):
_.all_res += 120
_.vitality += 650
def pants_1(_):
_.vitality += 650
_.armor += 700
_.resistances['physical'] += 210
def pants_2(_):
_.vitality += 650
_.all_res += 120
def bastions_1(_):#ring set
_.strength += 1000
_.cc += .12
_.cd += 1
_.resistances['physical'] += 210
_.resistances['poison'] += 210
_.bastions = True
def bastions_2(_):
_.strength += 500
_.cc += .12
_.cd += 1
_.cdr *= .92
_.resistances['physical'] += 210
_.resistances['poison'] += 210
_.bastions = True
def bk_1(_): # (str, dmg, cdr) + (str, cdr, vit)
_.strength += 2000
_.damage_mod *= 1.05
_.cdr *= .9 * .9
_.vitality += 1000
def bk_2(_): # (str, dmg, cdr) + (str, dmg, loh)
_.strength += 2000
_.damage_mod *= 1.1
_.cdr *= .9
_.life_on_hit += 18000
def best_score():
def available_items(prefix):
#automagically check barb for possible item variants of the item slot 'prefix'
# so more can be added at a later time
r = []
i = 1
while True:
name = '%s_%s'%(prefix, i)
if hasattr(Barb, name):
r.append(name)
else: return r
i += 1
gear_slots = [
'helm','torso','pants','bastions','bk']
helms, torso, pants, bastions, bk = [available_items(i) for i in gear_slots]
gears = itertools.product(helms, torso, pants, bastions, bk)
bestOffense = {'gear':[],
'health':0,
'mitigation':0,
'damage':0}
elapsed = time.time()
while True:
try:
args = next(gears)
barb = Barb(args)
armor = barb.armor * (1 + barb.armor_bonus_percent)
damage_reduction = armor / (armor + 3500)
resistances = {res:(barb.resistances[res] + barb.all_res) \
* (1 + barb.all_res_bonus_percent) for \
res in barb.resistances}
elemental_dr = {res:resistances[res]/(resistances[res] + 350) \
for res in resistances}
health = barb.vitality * 100 * (1 + barb.life_bonus_percent)
aps = barb.attacks_per_second * (1 + barb.ias)
damage_mod = barb.damage_mod * (1 + (barb.strength / 100))
damage_mod *= (1 - barb.cc) + (barb.cc * barb.cd)
damage_mod *= 2.25 if barb.bastions else 1
damage_mod *= 1 + barb.elemental_damage_bonus
dust_devils = 25 * damage_mod * (1 + barb.dibs + barb.ww_damage_percent)
min_elemental_dr = elemental_dr[min(elemental_dr)]
mitigation = 1 - ((1-damage_reduction) * (1-min_elemental_dr))
if dust_devils > bestOffense['damage']:
bestOffense = {'gear':args,
'health':health,
'mitigation':mitigation,
'damage':dust_devils}
except: return bestOffense, time.time() - elapsed
Python static methods will stop the interpreter making a new function in memory for every instance of a class. You can only use it for functions that don't need an instance to operate on though, i.e. functions that don't use self.
Related
I recently started learning AI and i watched this video of Code Bullet, presenting a very simple genetic algorithm of a simple game where dots need to reach to a goal. I wanted to recreate this game in python using pygame. Since it didn't work at all, I tried redesigning it a bit.
Here is the code:
import pygame as pg
from DotGame.DotGameFunctions import *
import random as r
import time
pg.init()
WIN_WIDTH = 500
WIN_HEIGHT = 500
win = pg.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pg.display.set_caption('DotGame')
SPAWN = Vector2(WIN_WIDTH / 2, WIN_HEIGHT - 40)
GOAL = Vector2(WIN_WIDTH / 2, 10)
class Dot:
def __init__(self, pos: Vector2):
self.pos = pos
self.dead = False
self.reachedGoal = False
self.is_best = False
self.fitness = 0
self.brain = Brain(1000)
self.vel = Vector2()
def move(self):
if self.brain.step < len(self.brain.directions):
self.vel = self.brain.directions[self.brain.step]
self.brain.step += 1
else:
self.dead = True
self.pos = self.pos + self.vel
def update(self):
if not self.dead and not self.reachedGoal:
self.move()
if self.pos.x < 5 or self.pos.y < 5 or self.pos.x > WIN_WIDTH - 5 or self.pos.y > WIN_HEIGHT - 5:
self.dead = True
elif dis(self.pos.x, self.pos.y, GOAL.x, GOAL.y) < 5:
self.reachedGoal = True
if self.is_best:
color = (0, 255, 0)
width = 7
else:
color = (0, 0, 0)
width = 5
if self.fitness > .005:
color = (0, 0, 255)
width = 7
pg.draw.circle(win, color, tuple(self.pos), width)
def calculate_fitness(self):
if self.reachedGoal:
self.fitness = 1 / 16 + 10000 / self.brain.step
else:
distance_to_goal = dis(self.pos.x, self.pos.y, GOAL.x, GOAL.y)
self.fitness = 1 / distance_to_goal
def make_baby(self):
baby = Dot(SPAWN)
baby.brain = self.brain.clone()
return baby
class Goal:
def __init__(self):
self.pos = GOAL
def update(self):
pg.draw.circle(win, (255, 0, 0), tuple(self.pos), 10)
class Brain:
def __init__(self, size):
self.size = size
self.step = 0
self.directions = []
self.randomize()
self.mutationRate = 1
def randomize(self):
self.directions = []
for i in range(self.size):
self.directions.append(Vector2.from_angle(r.random() * 2 * math.pi) * 4)
def clone(self):
clone = Brain(len(self.directions))
clone.directions = self.directions
return clone
def mutate(self):
for i in range(len(self.directions) - 1):
rand = r.random()
if rand < self.mutationRate:
self.directions[i] = Vector2.from_angle(r.random() * 2 * math.pi) * 4
class Population:
def __init__(self, size):
self.size = size
self.Dots = []
for i in range(size):
self.Dots.append(Dot(SPAWN))
self.minStep = 20
self.gen = 0
self.bestDot = 0
self.fit_sum = 0
def update(self):
for d in self.Dots:
d.update()
def fitness(self):
for d in self.Dots:
d.calculate_fitness()
def end_of_generation(self):
for d in self.Dots:
if not d.dead and not d.reachedGoal:
return False
return True
def natural_selection(self):
self.fitness()
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
new_dots = new_dots[:len(new_dots)//2] * 2
print([i.fitness for i in new_dots])
self.Dots = [d.make_baby() for d in new_dots]
self.Dots[0].is_best = True
for d in range(len(self.Dots) // 2, len(self.Dots)):
self.Dots[d].brain.mutate()
test = Population(100)
goal = Goal()
run = True
while run:
win.fill('white')
goal.update()
if test.end_of_generation():
test.natural_selection()
time.sleep(1)
else:
test.update()
# for event in pg.event.get():
# if event.type == pg.QUIT:
# run = False
pg.display.update()
time.sleep(0.005)
pg.quit()
The important part of this code is the natural_selection() function in the population class:
def natural_selection(self):
self.fitness()
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
new_dots = new_dots[:len(new_dots)//2] * 2
print([i.fitness for i in new_dots])
self.Dots = [d.make_baby() for d in new_dots]
self.Dots[0].is_best = True
for d in range(len(self.Dots) // 2, len(self.Dots)):
self.Dots[d].brain.mutate()
What it does (or supposed to do) is to calculate the fitness of the dots, sort the list of dots by fitness from highest to lowest, cuts the list in half and duplicating it, so the first and second halves are the same, and then sets the first dot to be the best and mutates the second half.
The problem is that as the print in line 6 shows, it doesn't really mutate the dots, and the result is that in every generation it just takes the same repeating list and sorts it, and the fitnesses look like this:
[0.003441755148372998, 0.0034291486755453414, 0.003070574887525978, 0.0030339596318951327, 0.003030855079397534,...]
[0.00481387362410465, 0.00481387362410465, 0.003468488512721536, 0.003468488512721536, 0.0032419920180191478,...]
[0.004356736047656708, 0.004356736047656708, 0.004356736047656708, 0.004356736047656708, 0.003056862712974015,...]
I checked the brain.mutate function and it seems to work just fine.
I've rewritten your natural_selection method to be a little more structured (to me at least), and it seems to work for my simple test case. Can you try if this works for you? I added some comments to explain my steps.
def natural_selection(self):
# Calculate fitness
self.fitness()
# Print initial generation
print(pop.Dots)
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = [d.make_baby() for d in best_half]
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = offspring + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
# Print new generation
self.fitness()
print(pop.Dots)
Full test case:
import random
# Brains only consist of some random number.
# Mutations just add one to that number.
class Brain:
def __init__(self):
self.num = self.randomize()
def clone(self):
clone = Brain()
clone.num = self.num
return clone
def randomize(self):
return random.randint(1000, 9000)
def mutate(self):
self.num += 1
# Dots consist of a brain an a fitness.
# Fitness is simply the brain's number.
class Dot:
def __init__(self):
self.brain = Brain()
self.fitness = None
def __repr__(self):
return str(self.fitness)
def make_baby(self):
baby = Dot()
baby.brain = self.brain.clone()
return baby
class Population:
def __init__(self):
self.Dots = [Dot() for i in range(10)]
def fitness(self):
for d in self.Dots:
d.fitness = d.brain.num
def natural_selection(self):
# Calculate fitness
self.fitness()
# Print initial generation
print(pop.Dots)
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = [d.make_baby() for d in best_half]
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = offspring + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
# Print new generation
self.fitness()
print(pop.Dots)
random.seed(1)
pop = Population()
pop.natural_selection()
Returns:
[2100, 5662, 7942, 7572, 7256, 1516, 3089, 1965, 5058, 7233]
[7942, 7572, 7256, 7233, 5662, 7943, 7573, 7257, 7234, 5663]
So the original population fitness is printed on the first line. In the second line, the mutated population fitness is shown. My mutations simply add one to the dots brain value, so the second half is a copy of the first half with 1 added to each value.
Moved from an edit to the question by the OP to an answer.
There is a problem with copying the list in natural_selection:
from copy import deepcopy
...
def natural_selection(self):
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = deepcopy(offspring)
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = best_half + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
I have 2 almost identical classes. How can they be solved by inheritance?
1 class (maybe parent):
class Heart():
""" třída zdraví """
def __init__(self):
self.x = []
self.y = []
self.s = []
self.h = 0
def vykresli(self):
#vykreslení srdce
heart_big = pygame.image.load(os.path.join(DIR, "images/heart_big.png"))
heart_small = pygame.image.load(os.path.join(DIR, "images/heart_small.png"))
if random.randrange(0, 500) == 1 and len(self.x) <= 0:
self.x.append(random.randrange(50, 970))
self.y.append(0)
self.s.append(random.randrange(1, 4))
for x in range(len(self.x)):
if self.h % 9 == 0:
SCREEN.blit(heart_big, (self.x[x], self.y[x]))
else:
SCREEN.blit(heart_small, (self.x[x], self.y[x]))
self.y[x] += self.s[x]
self.h += 1
if self.h > 100: self.h = 0
for y in range(len(self.x)):
if self.y[y] > OKNO_Y:
del self.y[y]
del self.x[y]
del self.s[y]
break
2 class (maybe child)
class MissileStack():
"""definuje třídu nabití zásobníku střelami"""
def __init__(self):
self.x = []
self.y = []
self.s = []
self.h = 0
self.rotate = 0
def vykresli(self):
#vykreslení dodávky zásoby střel
misilestack = pygame.image.load(os.path.join(DIR, "images/misilestack.png"))
if random.randrange(0, 50) == 1 and len(self.x) <= 0:
self.x.append(random.randrange(50, 970))
self.y.append(0)
self.s.append(random.randrange(1, 4))
#self.missile = pygame.transform.rotate(pygame.image.load(os.path.join(DIR, "images/missile.png")),180)
for x in range(len(self.x)):
misilestackrotate = pygame.transform.rotate(misilestack, self.rotate)
SCREEN.blit(misilestackrotate, (self.x[x], self.y[x]))
self.y[x] += self.s[x]
self.h += 1
self.rotate += 1
if self.rotate >= 360: self.rotate = 0
if self.h > 100: self.h = 0
for y in range(len(self.x)):
if self.y[y] > OKNO_Y:
del self.y[y]
del self.x[y]
del self.s[y]
break
Both classes do practically the same thing. They just use a different image and a different random generation. I've read a lot of tutorials on class inheritance, but I can't use inheritance in practice.
Ignoring the fact that your code is doing a bunch of questionable things, You can create a base class that factors out the common operations.
All the class and method names are completely made up here, since I don't know what you are really trying to do and my command of your native language is non-existent:
class Sprite:
def __init__(self, r):
self.x = []
self.y = []
self.s = []
self.r = r
def vykresli(self):
if random.randrange(0, self.r) == 1 and len(self.x) <= 0:
self.x.append(random.randrange(50, 970))
self.y.append(0)
self.s.append(random.randrange(1, 4))
self.pre_loop()
for x in range(len(self.x)):
self.do_blit((self.x[x], self.y[x]))
self.y[x] += self.s[x]
self.post_loop()
for y in range(len(self.x)):
if self.y[y] > OKNO_Y:
del self.y[y]
del self.x[y]
del self.s[y]
break
def pre_loop(self):
pass
def post_loop(self):
pass
The deltas now make for two small specific classes:
class Heart(Sprite):
def __init__(self):
super().__init__(500)
self.h = 0
#vykreslení srdce
self.heart_big = pygame.image.load(os.path.join(DIR, "images/heart_big.png"))
self.heart_small = pygame.image.load(os.path.join(DIR, "images/heart_small.png"))
def do_blit(self, coord):
if self.h % 9 == 0:
SCREEN.blit(self.heart_big, coord)
else:
SCREEN.blit(self.heart_small, coord)
def post_loop(self):
super().post_loop()
self.h += 1
if self.h > 100:
self.h = 0
and
class Missiles(Sprite):
def __init__(self):
super().__init__(50)
#vykreslení dodávky zásoby střel
misilestack = pygame.image.load(os.path.join(DIR, "images/misilestack.png"))
def pre_loop(self):
super().pre_loop()
self.misilestackrotate = pygame.transform.rotate(misilestack, self.rotate)
def do_blit(self, coord):
SCREEN.blit(self.misilestackrotate, coord)
def post_loop(self):
super().post_loop()
self.rotate += 1
if self.rotate >= 360:
self.rotate = 0
I've added hooks called pre_loop, do_blit and post_loop to allow you to do class-specific operations for each type of Sprite. You can experiment with abstract base classes if you want to do things like enforce an implementation of do_blit.
I'm trying to implement a genetic algorithm for solving the Travelling Salesman Problem (TSP).
I have 2 classes, which are City and Fitness.
I have done the code for initialization.
class City:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, city):
xDis = abs(self.x - city.x)
yDis = abs(self.y - city.y)
distance = np.sqrt((xDis ** 2) + (yDis ** 2))
return distance
def __repr__(self):
return "(" + str(self.x) + "," + str(self.y) + ")"
class Fitness:
def __init__(self, route):
self.route = route
self.distance = None
self.fitness = None
def routeDistance(self):
if self.distance == None:
pathDistance = 0.0
for i in range(0, len(self.route)):
fromCity = self.route[i]
toCity = None
if i+1 < len(self.route):
toCity = self.route[i+1]
else:
toCity = self.route[0]
pathDistance += fromCity.distance(toCity)
self.distance = pathDistance
return self.distance
def routeFitness(self):
if self.fitness == None:
self.fitness = 1 / float(self.routeDistance())
return self.fitness
def selection(population, size=None):
if size== None:
size= len(population)
matingPool = []
fitnessResults = {}
for i in range(0, size):
fitnessResults[i] = Fitness(population[i]).routeFitness()
matingPool.append(random.choice(population))
return matingPool
The code above just randomly selects a parent in the selection method.
My question is: How to code to select a parent using roulette wheels?
You could try this [1, 2]:
from numpy.random import choice
def selection(population, size=None):
if size== None:
size= len(population)
fitnessResults = []
for i in range(0, size):
fitnessResults.append(Fitness(population[i]).routeFitness())
sum_fitness = sum(fitnessResults)
probability_lst = [f/sum_fitness for f in fitnessResults]
matingPool = choice(population, size=size, p=probability_lst)
return matingPool
Read this
So basically, the higher a fitness value, the higher are its chances to be chosen. But that is when high fitness value means a high fitness. But in TSP a lower value of fitness is better so to implement this, we need to implement the concept where probability is indirectly proportional to the fitness value.
Here is something I had implemented in python with some changes
def choose_parent_using_RWS(genes, S):
P = random.uniform(0, S)
for x in genes:
P += get_fitness_value(x)
if P > S:
return x
return genes[-1]
where S is the sum of the inverse of the fitness values of the current population (i.e, 1/f1 + 1/f2 + 1/f3 + ...)
and
get_fitness_value(x) returns the inverse of the distance, just like your routeFitness() function
TeeHee
Im trying to subtract with prefixes as objects.
Here is my code
class Prefix:
def __init__(self, m=0, cm=0):
self.m = m
self.cm = cm
def __sub__(self, other):
centim = self.cm - other.cm
meter = (self.m - other.m) - abs(centim/100)
if meter < 1:
centim = m*100
meter = 0
return Prefix(meter, cm)
Im trying to subtract in a way which creates a negative centimeter value and take 1m from the meter object such that this is fulfilled
Prefix(2, 20) - Prefix(1, 30) == Prefix(0, 90)
First, keep in mind that for a given length, everything to the right of the hundreds place goes into cm, and everything at it or to its left gets divided by 100, and then goes into m.
Given this, we can recast the problem as converting both Prefix objects into their full lengths, performing calculations there, and then creating a new Prefix from the result:
class Prefix:
def __init__(self, m=0, cm=0):
self.m = m
self.cm = cm
def __sub__(self, other):
self_length = self.m * 100 + self.cm
other_length = other.m * 100 + other.cm
result_length = self_length - other_length
result_m, result_cm = divmod(result_length, 100)
return Prefix(result_m, result_cm)
result = Prefix(2, 20) - Prefix(1, 30)
print(result.m, result.cm)
Output:
0 90
Since we've come this far, we might as well store a "length" and overload __repr__ to make the result prettier:
class Prefix:
def __init__(self, length):
self.length = length
def __sub__(self, other):
result_length = self.length - other.length
return Prefix(result_length)
def __repr__(self):
result_m, result_cm = self.split_up(self.length)
if result_m > 0:
return f'{result_m}m {result_cm}cm'
else:
return f'{result_cm}cm'
#staticmethod
def split_up(length):
return divmod(length, 100)
Prefix(220) - Prefix(130)
Output:
90cm
I am wondering in the following code,
In the code below i is set to min child, why is this done instead of say just having i = i*2. Both ways 'lower' the level of the binary heap, I am just curious as to why the minimum would be chosen (Assuming both children are NOT smaller than parent, why is the smaller of the two larger children chosen instead of one of them arbitrarily)
To be clear these methods below also belong to the binaryheap class
def percDown(self,i):
while (i * 2) <= self.currentSize:
mc = self.minChild(i)
if self.heapList[i] > self.heapList[mc]:
tmp = self.heapList[i]
self.heapList[i] = self.heapList[mc]
self.heapList[mc] = tmp
i = mc
def minChild(self,i):
if i * 2 + 1 > self.currentSize:
return i * 2
else:
if self.heapList[i*2] < self.heapList[i*2+1]:
return i * 2
else:
return i * 2 + 1
The code for the binary heap class is
class BinHeap:
def __init__(self):
self.heapList = [0]
self.currentSize = 0
def delMin(self):
retval = self.heapList[1]
self.heapList[1] = self.heapList[self.currentSize]
self.currentSize = self.currentSize - 1
self.heapList.pop()
self.percDown(1)
return retval