One idea I want to explore (I don't know when) is to substitute most of the functions with dictionaries and func calls by lookups. I think this way I can represent the movements in a way similar to a rule system.
Solution in javascript. Probably it can be done more pretty, I feel like I was way too explicit with every move, but it worked. I do this by opening the tab with input, opening dev tools and pasting the code right in the console xD.
I remember something similar form last years AoC :)
Today most of my bugs were from me not knowing PHP (I'm using AoC to learn it). After JS I just keep forgetting that arrays are passed by value and you need to use pointer (&) to pass variable by reference.
<?php$input=require_once'readFile.php';functioncreateGridAndCarts($input){$carts=[];$grid=[];foreach($inputas$i=>$str){$str=preg_replace('/\n/','',$str);$gridLine=str_split($str);$cart=[];$isCart=['>'=>'-','v'=>'|','<'=>'-','^'=>'|'];foreach($gridLineas$key=>$value){if(in_array($value,array_keys($isCart))){$carts[]=['y'=>$i,'x'=>$key,'pos'=>$value,'cross'=>'right'];$gridLine[$key]=$isCart[$value];}$grid[$i]=$gridLine;}}return[$grid,$carts];}functionmove($cart,$grid){$turnR=['^'=>'>','>'=>'v','v'=>'<','<'=>'^'];$turnL=['^'=>'<','>'=>'^','v'=>'>','<'=>'v'];$nextCross=['left'=>'straight','straight'=>'right','right'=>'left'];$currentPath=$grid[$cart['y']][$cart['x']];$newPos=$cartPos=$cart['pos'];$cartUpDown=$cartPos=='^'||$cartPos=='v';$cartLeftRihgt=$cartPos=='>'||$cartPos=='<';if($currentPath=='+'){$lastCross=$cart['cross'];$cart['cross']=$nextCross[$lastCross];if($lastCross=='right'){$newPos=$turnL[$cart['pos']];}elseif($lastCross=='straight'){$newPos=$turnR[$cart['pos']];}}elseif($currentPath=='\\'){$cartUpDown&&$newPos=$turnL[$cartPos];$cartLeftRihgt&&$newPos=$turnR[$cartPos];}elseif($currentPath=='/'){$cartUpDown&&$newPos=$turnR[$cartPos];$cartLeftRihgt&&$newPos=$turnL[$cartPos];}$cart['pos']=$cartPos=$newPos;$cartPos=='>'&&$cart['x']++;$cartPos=='<'&&$cart['x']--;$cartPos=='^'&&$cart['y']--;$cartPos=='v'&&$cart['y']++;return$cart;}functionsortCarts($carts){usort($carts,function($c1,$c2){$sortY=$c1['y']<=>$c2['y'];if($sortY==0){return$c1['x']<=>$c2['x'];}return$sortY;});return$carts;}functionhasCollided($cart,$carts):bool{$collidors=0;foreach($cartsas$c){if($cart['x']==$c['x']&&$cart['y']==$c['y']){$collidors++;}}return$collidors>1;}functionfindCollisionId($carts,$ownId){$cart=$carts[$ownId];foreach($cartsas$key=>$val){if($key!=$ownId&&$cart['x']==$val['x']&&$cart['y']==$val['y']){return$key;}}returnNULL;}functionfindFirstCollision($grid,$carts){$collision=false;while(!$collision){foreach($cartsas$i=>$cart){$cart=move($cart,$grid);$carts[$i]=$cart;$collision=hasCollided($cart,$carts);if($collision){return$cart['x'].",".$cart['y'];}}}}functionfindLastCartStanding($grid,$carts){while(count($carts)>1){foreach($cartsas$i=>&$cart){if($cart==NULL){continue;}$cart=move($cart,$grid);$carts[$i]=$cart;$collision=hasCollided($cart,$carts);if($collision){$id=findCollisionId($carts,$i);$carts[$id]=NULL;$carts[$i]=NULL;}}$carts=sortCarts(array_filter($carts));}return$carts[0];}[$grid,$carts]=createGridAndCarts($input);echo"First collision at \t".findFirstCollision($grid,$carts)."\n";$cart=findLastCartStanding($grid,$carts);echo"Last cart stands at \t".$cart['x'].",".$cart['y']."\n";?>
readFile.php
<?php$file=fopen("input.txt","r")orexit("Unable to open file");while(!feof($file)){$array[]=fgets($file);}fclose($file);returnarray_filter($array);?>
Half way, and we have a puzzle that just needs cranked through. There's no trick that particularly helps - just read the input into a big 2D array, turn the cart symbols into little models of the carts that can maintain the necessary information about the next turn, etc., and run the ticks.
It can be done without a bunch of ugly and error-prone mutable state though. First a model:
data classPos(valx:Int,valy:Int)enumclassDir{LEFT,RIGHT,UP,DOWN}enumclassTurn{LEFT,STRAIGHT,RIGHT}data classPlan(valdir:Dir,valturn:Turn)data classCart(valid:Int,valpos:Pos,valplan:Plan,valcollided:Boolean=false)typealiasTrack=ChartypealiasTracks=List<CharArray>data classModel(valtracks:Tracks,valcarts:List<Cart>)
Loading the input data
The input text lines are just mapped to CharArrays. Then I use a pair of foldIndexed() calls run over the input on both axes and find the locations of the carts. A starting model for each cart is built here.
Finally I replace the cart positions with track characters. I could have left them in place and given the original characters the same semantics, I guess. No big deal.
The interesting one is Plan.adjust() which takes the current track character at a cart's position and builds a new plan for the cart. On intersections it applies turning logic, on curves it changes direction, etc. In some cases the plan doesn't change.
Each tick is implemented by moving the carts in scan order and checking for collisions. One tricky part of this is that the carts move one at a time, giving a bunch of inter-tick mini-states. So the initial state of the fold is the previous state's carts, and on each iteration of the fold one cart is replaced by its updated version. This ensures the correct collision comparisons are made.
A whole new model is built each time.
funCart.checkCollisions(carts:Collection<Cart>):Cart=if(carts.any{c->c.collided})this// only want the first collisionelseif(carts.none{c->c.pos==pos})thiselsethis.copy(collided=true)funModel.tick():Model{valnewCarts=cartsInScanOrder.fold(carts){carts_,c->valotherCarts=carts_.filter{it!=c}otherCarts+c.move(tracks).checkCollisions(otherCarts)}returnModel(tracks,newCarts)}
Running the simulation makes use of an infinite sequence of states I call the timeline:
A proper part 2 today, which introduced a new tricky problem. Removing the carts the instant they crash introduces some more complexity to those inter-tick mini-states. We're folding over the carts in scan order, but our fold accumulator is the updated list of carts. These can become out of sync as a collision can happen before the scan order reaches a certain cart, removing it from the simulation. I solved this in a kind-of hacky way by skipping any cart in the fold that has been removed from the list.
funModel.tick():Model{valnewCarts=cartsInScanOrder.fold(carts){carts_,c->if(!carts_.contains(c))// removed by collisioncarts_else{valc_=c.move(tracks)valotherCarts=carts_.filter{it!=c}valcrash=c_.collision(otherCarts)if(crash==null)otherCarts+c_elseotherCarts.filterNot{it==crash}}}returnModel(tracks,newCarts)}
The part 2 simulation is similar to part 1. Drop states until the end criteria is met, then extract the information required from the next state.
This is one of those problems that you just gotta grind through, I guess.
Surprisingly, this is the first one where, the first time it compiled, I got the right answer. Of course, getting it to compile took a few tries-- I'm using AoC to learn a new language --but once the syntax issues were ironed out, I just had the solution ready to go.
Only really interesting things are that on the beginning of each tick, I can do
sort!(board.carts,by=c->(c.y,c.x))
which sorts the array of carts first by y, then by x. Then I can just iterate straight through that array and have the carts in correct order.
I implemented collision checking like so
function check_collisions(carts)positions=Set()forcartincartspos=(cart.x,cart.y)ifposinpositionsreturnposelsepush!(positions,pos)endendreturnnothingend
and sadly have to call it once per cart, per tick. If I only called this at the beginning or end of each tick, then two carts like so:
--><--
would actually end up switching spots, and I'd never notice they overlapped.
For part 2, I replaced my check_collisions method with
function remove_collisions(carts)positions=Dict{Tuple{Int,Int},Array{Cart}}()forcartincartspos=(cart.x,cart.y)if!haskey(positions,pos)positions[pos]=[]endpush!(positions[pos],cart)endcarts::Array{Cart}=[]forcinvalues(positions)iflength(c)==1push!(carts,c[1])endendreturncartsend
Essentially, instead of merely detecting if there is a collision, this method filters out any carts that have a position that is equal. (Now that I type that out, I realize I could have just called filter()...oh well.)
I'm still calling this once per cart per tick, which is so poorly inefficient that it makes me wince, but there are so few carts in the input that it rarely matters.
Oh, and last lesson learned: I wrote a print_board() function that didn't actually print the carts! This is really really really stupid...it made that function almost entirely useless.
const{readFile}=require('./reader');const{buildMap,buildPath}=require('./13-common');constgetSquareCrash=carts=>{while(true){carts.sort((a,b)=>{constsA=a.square;constsB=b.square;return(sA.x===sB.x)?sA.y-sB.y:sA.x-sB.x;});for(letcartofcarts){cart.move();if(cart.crashed){returncart.square;}}};};(async()=>{constlines=awaitreadFile('13-input.txt');constmap=buildMap(lines);const{squares,carts}=buildPath(map);constsquare=getSquareCrash(carts);console.log(`The location of the first crash is ${square.y},${square.x}`);})();
13b.js
const{readFile}=require('./reader');const{buildMap,buildPath}=require('./13-common');constgetRemainingCart=carts=>{lettick=0;while(carts.length>1){carts.sort((a,b)=>{constsA=a.square;constsB=b.square;return(sA.x===sB.x)?sA.y-sB.y:sA.x-sB.x;});for(letcartofcarts){if(!cart.crashed){cart.move();if(cart.crashed){cart.square.cart=null;}}}carts=carts.filter(cart=>!cart.crashed);tick++};returncarts[0];};(async()=>{constlines=awaitreadFile('13-input.txt');constmap=buildMap(lines);const{squares,carts}=buildPath(map);constcart=getRemainingCart(carts);console.log(`The location of the last cart ${cart.id} is ${cart.square.y},${cart.square.x}`);})();
This one seemed like the hardest one so far on first reading, but I ended up grasping it better than a couple of the previous puzzles. I ended up using a deque to make the turning directions easier to muck with:
Perl solution. Most of the decisions are represented by the hash tables at the beginning of the code:
#!/usr/bin/perlusewarnings;usestrict;usefeatureqw{ say };useList::Utilqw{ first };useenumqw( X Y FACING TURN );useenumqw( LEFT STRAIGHT RIGHT );useconstant{MOVE=>{'<'=>[-1,0],'>'=>[1,0],'v'=>[0,1],'^'=>[0,-1],},BEND=>{'^/'=>'>','^\\'=>'<','v/'=>'<','v\\'=>'>','</'=>'v','<\\'=>'^','>/'=>'^','>\\'=>'v',},FACES=>['^','>','v','<'],};my(@map,@carts,%crash);while(<>){chomp;while(/([<>^v])/g){push@carts,[pos()-1,$.-1,$1,LEFT];++$crash{$carts[-1][X]}{$carts[-1][Y]};}s/[<>]/-/g;s/[v^]/|/g;push@map,[split//,$_,-1];}while(1){formy$cart(sort{$a->[Y]<=>$b->[Y]||$a->[X]<=>$b->[X]}@carts){--$crash{$cart->[X]}{$cart->[Y]};my$move=MOVE->{$cart->[FACING]};$cart->[$_]+=$move->[$_]forX,Y;if($crash{$cart->[X]}{$cart->[Y]}++){say"$cart->[X],$cart->[Y]";exit}my$current=$map[$cart->[Y]][$cart->[X]];nextif$current=~/[-|]/;if('+'eq$current){my$face_index=first{$cart->[FACING]eqFACES->[$_]}0..$#{(FACES)};$face_index+=(-1,0,1)[$cart->[TURN]];$cart->[FACING]=FACES->[$face_index%4];++$cart->[TURN];$cart->[TURN]%=3;}else{$cart->[FACING]=BEND->{$cart->[FACING].$current};}}}
In part 2, I just replaced the code that printed the collision with a new one that removes the two carts involved.
#!/usr/bin/perlusewarnings;usestrict;usefeatureqw{ say };useList::Utilqw{ first };useenumqw( X Y FACING TURN );useenumqw( LEFT STRAIGHT RIGHT );useconstant{MOVE=>{'<'=>[-1,0],'>'=>[1,0],'v'=>[0,1],'^'=>[0,-1],},BEND=>{'^/'=>'>','^\\'=>'<','v/'=>'<','v\\'=>'>','</'=>'v','<\\'=>'^','>/'=>'^','>\\'=>'v',},FACES=>['^','>','v','<'],};my@map;my@carts;my%crash;while(<>){chomp;while(/([<>^v])/g){push@carts,[pos()-1,$.-1,$1,LEFT];++$crash{$carts[-1][X]}{$carts[-1][Y]};}s/[<>]/-/g;s/[v^]/|/g;push@map,[split//,$_,-1];}while(1){formy$cart(sort{$a->[Y]<=>$b->[Y]||$a->[X]<=>$b->[X]}@carts){nextunless$cart->[FACING];--$crash{$cart->[X]}{$cart->[Y]};my$move=MOVE->{$cart->[FACING]};$cart->[$_]+=$move->[$_]forX,Y;if($crash{$cart->[X]}{$cart->[Y]}++){$crash{$cart->[X]}{$cart->[Y]}-=2;$cart->[FACING]=0;my$other_cart=first{$_->[FACING]&&$_->[X]==$cart->[X]&&$_->[Y]==$cart->[Y]}@carts;$other_cart->[FACING]=0;next}my$current=$map[$cart->[Y]][$cart->[X]];nextif$current=~/[-|]/;if('+'eq$current){my$face_index=first{$cart->[FACING]eqFACES->[$_]}0..$#{(FACES)};$face_index+=(-1,0,1)[$cart->[TURN]];$cart->[FACING]=FACES->[$face_index%4];++$cart->[TURN];$cart->[TURN]%=3;}else{$cart->[FACING]=BEND->{$cart->[FACING].$current};}}my@left=grep$carts[$_][FACING],0..$#carts;nextunless1==@left;sayjoin',',@{$carts[$left[0]]}[X,Y];exit}
I'm a Staff Software Engineer at Cisco. I specialize in Python and Go, building functional, practical, and maintainable web systems leveraging Kubernetes and the cloud. Blog opinions are my own.
Alright, now I'm making up ground on the ones I got behind on. I actually really enjoyed this one! It was fun tuning things to be readable and easy to follow.
"""Day 13: Mine Cart Madness
Figure out when carts on tracks will crash
"""fromtypingimportListclassVector:"""A 2D vector. Right is +X, Down is +Y"""def__init__(self,x,y):self.x=xself.y=ydef__add__(self,other):returnVector(self.x+other.x,self.y+other.y)def__eq__(self,other):returnself.x==other.xandself.y==other.ydef__repr__(self):returnf"Vector({self.x}, {self.y})"def__str__(self):returnf"<{self.x}, {self.y}>"def__hash__(self):returnhash((self.x,self.y))NORTH=Vector(0,-1)SOUTH=Vector(0,1)EAST=Vector(1,0)WEST=Vector(-1,0)DIRECTIONS=[NORTH,EAST,SOUTH,WEST]classCart:"""A cart that drives around the mine"""def__init__(self,position:Vector,direction:Vector):self.position=positionself.direction=directionself.next_turn="left"defmove(self):"""Updates position based on direction"""self.position+=self.directiondefturn_right(self):self.direction=DIRECTIONS[(DIRECTIONS.index(self.direction)+1)%len(DIRECTIONS)]defturn_left(self):self.direction=DIRECTIONS[(DIRECTIONS.index(self.direction)-1)%len(DIRECTIONS)]defhandle_next_turn(self):"""Update own direction based on the next turn value specified in the prompt"""ifself.next_turn=="left":self.turn_left()self.next_turn="straight"elifself.next_turn=="straight":self.next_turn="right"elifself.next_turn=="right":self.turn_right()self.next_turn="left"classMine:"""A mine that has a bunch of different carts on tracks"""def__init__(self,map:str):self.map=[]self.carts:List[Cart]=[]fory,lineinenumerate(map.splitlines()):self.map.append([])forx,cinenumerate(line):ifc=='>':self.carts.append(Cart(Vector(x,y),EAST))self.map[-1].append('-')elifc=='<':self.carts.append(Cart(Vector(x,y),WEST))self.map[-1].append('-')elifc=='^':self.carts.append(Cart(Vector(x,y),NORTH))self.map[-1].append('|')elifc=='v':self.carts.append(Cart(Vector(x,y),SOUTH))self.map[-1].append('|')else:self.map[-1].append(c)deffind_first_collision(self)->Vector:"""Simulates the mine until two carts collide. Returns that x, y pair"""whileTrue:self.sort_carts()forcartinself.carts:cart.move()self.turn_cart(cart)ifself.is_collision(cart):returncart.positiondeflast_cart_location(self)->Vector:"""Simulates the mine, removing carts when they collide.
Returns the x, y pair of the last cart standing
"""whileTrue:self.sort_carts()crashed=set()forcartinself.carts:ifcart.positionincrashed:continuecart.move()self.turn_cart(cart)ifself.is_collision(cart):crashed.add(cart.position)continueself.carts=[cartforcartinself.cartsifcart.positionnotincrashed]iflen(self.carts)==1:returnself.carts[0].positiondefsort_carts(self):"""Carts are always run in reading order, top to bottom, left to right"""self.carts.sort(key=lambdacart:(cart.position.y,cart.position.x))defis_collision(self,cart:Cart)->bool:"""Checks whether or not a cart is running into any other carts"""returnsum(other.position==cart.positionforotherinself.carts)>1defturn_cart(self,cart:Cart):"""Depending on what kind of track the cart is on, turn it appropriately"""track_type=self.map[cart.position.y][cart.position.x]iftrack_type=='+':cart.handle_next_turn()eliftrack_type=='/':ifcart.direction==NORTHorcart.direction==SOUTH:cart.turn_right()else:cart.turn_left()eliftrack_type=='\\':ifcart.direction==NORTHorcart.direction==SOUTH:cart.turn_left()else:cart.turn_right()if__name__=="__main__":withopen("python/data/day13.txt","r")asf:mine_map=f.read()# Part 1
mine=Mine(mine_map)print(mine.find_first_collision())# Part 2
mine=Mine(mine_map)print(mine.last_cart_location())
A solution in python in which I have represented the tracks as an array of transformation functions on carts.
Really interesting how yours is very much the same concept as mine but Pythonic rather than Kotlin-ic!
github.com/neilgall/adventofcode20...
One idea I want to explore (I don't know when) is to substitute most of the functions with dictionaries and func calls by lookups. I think this way I can represent the movements in a way similar to a rule system.
Solution in javascript. Probably it can be done more pretty, I feel like I was way too explicit with every move, but it worked. I do this by opening the tab with input, opening dev tools and pasting the code right in the console xD.
This.Was.Awful.
I like the declarative section at the beginning (and used a similar one in my solution below).
I remember something similar form last years AoC :)
Today most of my bugs were from me not knowing PHP (I'm using AoC to learn it). After JS I just keep forgetting that arrays are passed by value and you need to use pointer (
&) to pass variable by reference.readFile.php
Half way, and we have a puzzle that just needs cranked through. There's no trick that particularly helps - just read the input into a big 2D array, turn the cart symbols into little models of the carts that can maintain the necessary information about the next turn, etc., and run the ticks.
It can be done without a bunch of ugly and error-prone mutable state though. First a model:
Loading the input data
The input text lines are just mapped to
CharArrays. Then I use a pair offoldIndexed()calls run over the input on both axes and find the locations of the carts. A starting model for each cart is built here.Finally I replace the cart positions with track characters. I could have left them in place and given the original characters the same semantics, I guess. No big deal.
Part 1
Cart movement is broken into a bunch of little functions, each of which should be pretty easy to understand.
The interesting one is
Plan.adjust()which takes the current track character at a cart's position and builds a new plan for the cart. On intersections it applies turning logic, on curves it changes direction, etc. In some cases the plan doesn't change.Moving a cart is a matter of determining the new plan based on the current track character, then executing the plan.
Scanning for the order to move the carts makes use of a nice Kotlin library feature of chainable comparators:
Each tick is implemented by moving the carts in scan order and checking for collisions. One tricky part of this is that the carts move one at a time, giving a bunch of inter-tick mini-states. So the initial state of the fold is the previous state's carts, and on each iteration of the fold one cart is replaced by its updated version. This ensures the correct collision comparisons are made.
A whole new model is built each time.
Running the simulation makes use of an infinite sequence of states I call the timeline:
Just drop states from this sequence until a collision is detected, then find the crashed cart:
Part 2
A proper part 2 today, which introduced a new tricky problem. Removing the carts the instant they crash introduces some more complexity to those inter-tick mini-states. We're folding over the carts in scan order, but our fold accumulator is the updated list of carts. These can become out of sync as a collision can happen before the scan order reaches a certain cart, removing it from the simulation. I solved this in a kind-of hacky way by skipping any cart in the fold that has been removed from the list.
The part 2 simulation is similar to part 1. Drop states until the end criteria is met, then extract the information required from the next state.
This is one of those problems that you just gotta grind through, I guess.
Surprisingly, this is the first one where, the first time it compiled, I got the right answer. Of course, getting it to compile took a few tries-- I'm using AoC to learn a new language --but once the syntax issues were ironed out, I just had the solution ready to go.
Only really interesting things are that on the beginning of each tick, I can do
which sorts the array of carts first by
y, then byx. Then I can just iterate straight through that array and have the carts in correct order.I implemented collision checking like so
and sadly have to call it once per cart, per tick. If I only called this at the beginning or end of each tick, then two carts like so:
would actually end up switching spots, and I'd never notice they overlapped.
For part 2, I replaced my
check_collisionsmethod withEssentially, instead of merely detecting if there is a collision, this method filters out any carts that have a position that is equal. (Now that I type that out, I realize I could have just called
filter()...oh well.)I'm still calling this once per cart per tick, which is so poorly inefficient that it makes me wince, but there are so few carts in the input that it rarely matters.
Full code here: github.com/MustafaHaddara/advent-o... and github.com/MustafaHaddara/advent-o...
Oh, and last lesson learned: I wrote a
print_board()function that didn't actually print the carts! This is really really really stupid...it made that function almost entirely useless.JavaScript solution
I had to do other things since Wednesday and couldn't focus on AoC until yesterday night but hopefully I will catch up soon!
I created a graph for all the map squares, and a list of carts that move around these squares.
Here's my solutions:
13-common.js
13a.js
13b.js
This one seemed like the hardest one so far on first reading, but I ended up grasping it better than a couple of the previous puzzles. I ended up using a
dequeto make the turning directions easier to muck with:Perl solution. Most of the decisions are represented by the hash tables at the beginning of the code:
In part 2, I just replaced the code that printed the collision with a new one that removes the two carts involved.
Alright, now I'm making up ground on the ones I got behind on. I actually really enjoyed this one! It was fun tuning things to be readable and easy to follow.
I promise I'll post my code eventually, I spent too long working on this animation of part1: gfycat.com/EasygoingMassiveHog
Oof, I played with this too long.
Here's it all.
Kotlin Solution