◂ 15 décembre 2015 ▸
Science for Hungry People :
- code.py
- codeRepartitionNonRecursif.py
- codeRepartitionOneLiner.py
- maths.py
import itertools
import numpy.linalg
TOT_QUANTITES = 100
NB_CALORIES_VOULUES = 500
STATS_INGREDIENTS = {
'Sprinkles': {'capacity':2, 'durability':0, 'flavor':-2, 'texture':0 , 'calories':3},
'Butterscotch': {'capacity':0, 'durability':5, 'flavor':-3, 'texture':0 , 'calories':3},
'Chocolate': {'capacity':0, 'durability':0, 'flavor':5 , 'texture':-1, 'calories':8},
'Candy': {'capacity':0, 'durability':-1, 'flavor':0 , 'texture':5 , 'calories':8}
}
# Trouver domaine, si x=Sprinkles, y=Butterscotch, z=Chocolate, t=Candy = TOT_QUANTITES - x - y -z
# contraintes de quantité max 100
inequations = [ # [a,b,c,d] => ax + by + cz ≤ d
[-1,0,0,0], # x ≥ 0
[0,-1,0,0], # y ≥ 0
[0,0,-1,0], # z ≥ 0
[1,1,1,TOT_QUANTITES], # t ≥ 0 <=> TOT_QUANTITES - x - y - z ≥ 0
]
# contraintes de score non négatif
for spec in ['capacity', 'durability', 'flavor', 'texture']:
a = STATS_INGREDIENTS['Sprinkles'][spec]
b = STATS_INGREDIENTS['Butterscotch'][spec]
c = STATS_INGREDIENTS['Chocolate'][spec]
e = STATS_INGREDIENTS['Candy'][spec]
if any([v < 0 for v in [a,b,c,e]]): # pas de contrainte si aucun ingrédient n'a d'effet négatif, car jamais de somme en dessous de zéro
inequations.append([e-a, e-b, e-c, e*TOT_QUANTITES]) # ax + by + cz + e(TOT-x-y-z) ≥ 0 (la somme des qualités n'est pas négative)
def resoudre(equations):
try:
sol = numpy.linalg.solve([ equ[:-1] for equ in equations ], [ equ[-1] for equ in equations ])
return list(sol)
except numpy.linalg.LinAlgError:
return None
def okContraintes(valeurs, inequation):
return sum([valeurs[i] * inequation[i] for i in range(len(valeurs))]) <= inequation[-1] + 0.0000001
print("Sommets du domaine:")
sommets = []
for triplet in itertools.combinations(range(len(inequations)), 3):
solution = resoudre([inequations[i] for i in triplet])
if solution and all([okContraintes(solution, inequ) for inequ in inequations]):
print(" ", [round(v,5) for v in solution])
sommets.append(solution)
# Partir du milieu du domaine (les bords correspondant aux contraintes de score non négatifs donnent forcément un score de 0, donc pas optimal comme départ)
depart = [round(numpy.average([s[i] for s in sommets])) for i in range(3)]
# Le milieu est déjà la solution… c'est en partie une coïncidence, donc on se pose au milieu de la face correspondant à une contrainte de quantité)
depart = [round(numpy.average([s[i] for s in sommets[0:3]])) for i in range(3)]
print("Depart en", depart)
def pointToQuantites(point):
return {'Sprinkles':point[0], 'Butterscotch':point[1], 'Chocolate':point[2], 'Candy':TOT_QUANTITES-sum(point)}
def scorePoint(point):
return score(pointToQuantites(point))
def score(quantites):
score = 1
for propriete in ['capacity', 'durability', 'flavor', 'texture']:
somme = 0
for ingredient in quantites:
somme += quantites[ingredient] * STATS_INGREDIENTS[ingredient][propriete]
score *= max(0, somme)
return score
point = depart
maxScore = scorePoint(point)
mieuxAilleurs = True
while mieuxAilleurs:
mieuxAilleurs = False
for numAxe in range(3):
for deplacement in [-1, 1]:
newPoint = point[:]
newPoint[numAxe] += deplacement
if newPoint[numAxe] < 0 or sum(newPoint) > TOT_QUANTITES:
continue
newScore = scorePoint(newPoint)
if newScore > maxScore:
mieuxAilleurs = True
maxScore = newScore
point = newPoint
print("Trouvé meilleur point:", point)
break
if mieuxAilleurs:
break
print(pointToQuantites(point), maxScore)