D'abord la modélisation, avec des opérations sur triplets de matière Matters(ore, clay, obsidian). Dans un premier temps j'avais aussi geode, mais en vrai on ne produit, ne consomme, ni ne stocke de geode : c'est le score, on le gère à part.
En vrai ça change pas grand chose...
Un frozen dataclass, et les opérations retournent une nouvelle instance, ça permet d'éviter des risques de modifier un truc référencé ailleurs, et on y gagne en bugs et en perfs au bout du compte.
Chaque blueprint génère une Factory qui ne sert pas à grand chose, c'est des données, c'est figé, ça bouge pas.
Ensuite une classe d'état, State, qui stocke le temps restant, les stocks, la production, et le nombre de géodes à la fin si on touche plus à rien, donc le score actuel de cet état à la fin du temps imparti. Aussi un dataclass, je découvre, j'en mets partout, selon le biais bien connu de waooouh-nouveauté !
importsysimportreimportmathfromdataclassesimportdataclassfromfunctoolsimportcached_property@dataclass(frozen=True)classMatters():ore:int=0clay:int=0obsidian:int=0def__add__(self,other):returnMatters(self.ore+other.ore,self.clay+other.clay,self.obsidian+other.obsidian,)def__sub__(self,other):returnMatters(self.ore-other.ore,self.clay-other.clay,self.obsidian-other.obsidian,)def__mul__(self,t):# Calculate production in t minutesreturnMatters(self.ore*t,self.clay*t,self.obsidian*t,)def__call__(self,name):returnself.__dict__[name]@dataclass(frozen=True)classFactory:id:introbots:dict[Matters]@cached_propertydefmaxproduction(self):returnMatters(*(max(x)forxinzip(*[(m.ore,m.clay,m.obsidian)forminself.robots.values()])))@dataclassclassState:timeleft:intstock:Matters=Matters()production:Matters=Matters(1,0,0)geode:int=0def__lt__(self,other):returnself.geode<other.geodedefbuildable(self):return[(robot,self.factory.robots[robot],self.test_build_time)forrobotin["geode","obsidian","clay","ore"]ifself.isbuilduseful(self.factory.robots[robot])]defisbuilduseful(self,cost):t=self.timetobuild(cost)iftisFalse:returnFalse# This robot should have the time to produce somethingift>=self.timeleft:returnFalseself.test_build_time=treturnTruedeftimetobuild(self,cost):t=0ifcost.ore>self.stock.ore:t=max(math.ceil((cost.ore-self.stock.ore)/self.production.ore),t)ifcost.clay>self.stock.clay:ifnotself.production.clay:returnFalset=max(math.ceil((cost.clay-self.stock.clay)/self.production.clay),t)ifcost.obsidian>self.stock.obsidian:ifnotself.production.obsidian:returnFalset=max(math.ceil((cost.obsidian-self.stock.obsidian)/self.production.obsidian),t)returnt+1# Time to collect enough resources, +1 to build the robotdefbuildrobot(self,robot,cost,time):stock=self.stock+self.production*time-costtime=self.timeleft-timeifrobot=="geode":returnState(time,stock,self.production,self.geode+time)returnState(time,stock,self.production+Matters(**{robot:1}),self.geode)def__str__(self):returnf"State Score={self.geode}, TimeLeft={self.timeleft}, "\
f"Production={self.production}, Stocks={self.stock}"
Ensuite le code en lui-même :
defiteration(state):buildable=state.buildable()ifnotbuildable:# end of the line !returnstate,1explored=0r=stateifbuildable[0][0]=="geode"andbuildable[0][2]==1:buildable=buildable[:1]forrobot,cost,timeinbuildable:ifrobot!="geode"andstate.production(robot)>=state.factory.maxproduction(robot):continues,e=iteration(state.buildrobot(robot,cost,time))explored+=er=sifr<selserifnotexplored:# robot limit attainedstate,1returnr,exploreddefinput():rematter=r"ore|clay|obsidian|geode"rerobot=re.compile(fr"Each ({rematter}) robot costs (.*)")recost=re.compile(fr"(\d+) ({rematter})")forblueprintinsys.stdin:id,blueprint=blueprint.strip().split(":")yieldFactory(int(id.split()[-1]),{build:Matters(**{b:int(a)fora,binrecost.findall(cost)})forrobotinblueprint.strip(".").split(".")forbuild,costin(rerobot.match(robot.strip()).groups(),)})defex1(factories):score=0expl=0forfinfactories:State.factory=fbeststate,explored=iteration(State(timeleft=24))print(f"Blueprints#{f.id} Best of {explored} State {str(beststate)}")score+=f.id*beststate.geodeexpl+=exploredprint(f"Score final = {score} (33, 1599) {expl} chemins explorés")defex2(factories):score=1expl=0forfinfactories:State.factory=fbeststate,explored=iteration(State(timeleft=32))print(f"Blueprints#{f.id} Best of {explored} State {str(beststate)}")score*=beststate.geodeexpl+=exploredprint(f"Score final = {score} ({56*62}, {49*18*16}) {expl} chemins explorés")factories=list(input())ex1(factories)ex2(fforfinfactoriesiff.id<=3)
# Du code, du code, du code !
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 19. Évalué à 4.
D'abord la modélisation, avec des opérations sur triplets de matière Matters(ore, clay, obsidian). Dans un premier temps j'avais aussi geode, mais en vrai on ne produit, ne consomme, ni ne stocke de geode : c'est le score, on le gère à part.
En vrai ça change pas grand chose...
Un frozen dataclass, et les opérations retournent une nouvelle instance, ça permet d'éviter des risques de modifier un truc référencé ailleurs, et on y gagne en bugs et en perfs au bout du compte.
Chaque blueprint génère une Factory qui ne sert pas à grand chose, c'est des données, c'est figé, ça bouge pas.
Ensuite une classe d'état, State, qui stocke le temps restant, les stocks, la production, et le nombre de géodes à la fin si on touche plus à rien, donc le score actuel de cet état à la fin du temps imparti. Aussi un dataclass, je découvre, j'en mets partout, selon le biais bien connu de waooouh-nouveauté !
Ensuite le code en lui-même :
Voilà voilà...