Voilà, avec une implémentation des unions d'intervalles sécants.
from__future__importannotationsimportrefromcollectionsimportnamedtuplefromtypingimportTuple,Iterable,List,Optional,TypeimportaocCoords=Tuple[int,int]classPoint(namedtuple('Point',['x','y'])):__slots__=()def__str__(self)->str:return"({},{})".format(self.x,self.y)defdist(self,other:Point)->int:returnabs(self.x-other.x)+abs(self.y-other.y)classInterval:def__init__(self,start:int,end:int)->None:ifstart>=end:raiseValueError("unsupported empty or negative interval")self.start=startself.end=enddef__str__(self)->str:return"[{},{}[".format(self.start,self.end)def__len__(self)->int:returnself.end-self.startdef__contains__(self,value:int)->bool:returnvalue>=self.startandvalue<self.enddefintersects(self,other:Interval)->bool:returnmax(self.start,other.start)<min(self.end,other.end)defunion_update(self,other:Interval)->bool:ifnotself.intersects(other):returnFalseself.start=min(self.start,other.start)self.end=max(self.end,other.end)returnTrueclassSensor:def__init__(self,position:Point,beacon:Point):self.position=positionself.beacon=beaconself.range=position.dist(beacon)defcovers(self,y:int)->bool:dist=abs(y-self.position.y)returndist<=self.rangedefcoverage(self,y:int)->Optional[Interval]:ifnotself.covers(y):returnNoneelse:dist=abs(y-self.position.y)returnInterval(self.position.x-self.range+dist,self.position.x+self.range+1-dist)re_line=re.compile(r'^Sensor at x=(-?\d+), y=(-?\d+): '+r'closest beacon is at x=(-?\d+), y=(-?\d+)\n?$')@classmethoddefimport_line(class_,line:str)->Sensor:match=class_.re_line.match(line)ifmatchisNone:raiseValueError("invalid sensor description")position=Point(int(match.group(1)),int(match.group(2)))beacon=Point(int(match.group(3)),int(match.group(4)))returnclass_(position,beacon)classSensorArray:def__init__(self,sensors:Iterable[Sensor]):self.sensors=list(sensors)@classmethoddefimport_lines(class_,lines:Iterable[str])->SensorArray:returnclass_(Sensor.import_line(line)forlineinlines)defcoverage(self,y:int)->List[Interval]:intervals:List[Interval]=[]forsensorinself.sensors:ifnotsensor.covers(y):continuenew_interval=sensor.coverage(y)ifnew_intervalisNone:continuenew_intervals:List[Interval]=[]forintervalinintervals:ifnotnew_interval.union_update(interval):# new_interval did not absorb intervalnew_intervals.append(interval)new_intervals.append(new_interval)intervals=new_intervalsreturnintervalsdefsolve1(lines:Iterable[str])->int:"""Solve part 1 of today's puzzle"""array=SensorArray.import_lines(lines)beacons={sensor.beaconforsensorinarray.sensors}y=2000000coverage=array.coverage(y)total=0forintervalincoverage:total+=len(interval)total-=sum((beacon.y==yandbeacon.xininterval)forbeaconinbeacons)returntotaldefsolve2(lines:Iterable[str])->int:"""Solve part 2 of today's puzzle"""array=SensorArray.import_lines(lines)position:Optional[Point]=Noneforyinrange(4000000):ify%100000==0:print(y)coverage=array.coverage(y)iflen(coverage)<2:continuereturnmin(interval.endforintervalincoverage)*4000000+yraiseValueError("no solution found")
# Unions d'intervalles
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Avent du Code jour 15. Évalué à 3.
Voilà, avec une implémentation des unions d'intervalles sécants.