Here is a quick and dirty (and slow!) Python solution. I will later translate it to Golang, but I doubt it will become any faster since it already uses dictionaries (hashmaps). What slows it down is probably iterating through each coordinate individually.
withopen('input')asf:data=f.readlines()defcalculate_distance(p1,p2):x1,y1=p1x2,y2=p2returnabs(x1-x2)+abs(y1-y2)# Find the outer border: P=set()# Contains the points outer_top,outer_bottom=-1,1000outer_left,outer_right=1000,-1fordindata:y,x=map(int,d[:-1].split(','))P.add((x,y))outer_top=max(outer_top,y)outer_bottom=min(outer_bottom,y)outer_left=min(outer_left,x)outer_right=max(outer_right,x)# Map coordinates to closest points and count: count_P={}# Counts for each point number of assigned unique closest coordinates canvas={}# Contains total distance to all points for coordinates foryinrange(outer_bottom,outer_top+1):forxinrange(outer_left,outer_right+1):canvas[(x,y)]=0min_dist=float('inf')forpinP:dist=calculate_distance((x,y),p)canvas[(x,y)]+=distifdist==min_dist:min_p=Noneelifdist<min_dist:min_p=pmin_dist=distifmin_p:count_P[min_p]=count_P.get(min_p,0)+1ifyin(outer_top,outer_bottom) \ orxin(outer_left,outer_right):# Mark point as having infinite area to not count count_P[min_p]=float('-inf')# Part 1: print('Part 1:')print('Point with largest area: {}'.format(max(count_P,key=count_P.get)))print('The area is: {}\n'.format(max(count_P.values())))# Part 2: region=[]forp,dincanvas.items():ifd<10000:region.append(p)print('Part 2:')print('Size of region: {}'.format(len(region)))
fromcollectionsimportdefaultdictfromcollectionsimportCounterfromitertoolsimportproductimportnumpyasnpfromscipy.spatialimportKDTreewithopen("input.txt")asf:coords=[(int(x.split(",")[0]),int(x.split(",")[1]))forxinf]xs,ys=[xforx,yincoords],[yforx,yincoords]# Part 1 t=KDTree(coords)d=defaultdict(int)edge=set()fori,jinproduct(range(max(xs)),range(max(ys))):points,dists=t.query((i,j),k=2,p=1)ifi==0orj==0ori==max(xs)-1orj==max(ys)-1:edge.add(int(dists[0]))ifpoints[0]!=points[1]:d[(i,j)]=dists[0]forp,areainCounter(d.values()).most_common():ifpnotinedge:print(area)break# Part 2 defdist(x,y):returnsum(abs(x-a)+abs(y-b)fora,bincoords)print(sum(1fori,jinproduct(range(max(xs)),range(max(ys)))ifdist(i,j)<10000))
Here is the Golang code. Even though it uses the exact same algorithm, it runs surprisingly faster!
packagemainimport("bufio""fmt""math""os""strconv""strings")typecoordstruct{xintyint}// readLines reads a whole file into memory// and returns a slice of its lines.funcreadLines(pathstring)([]string,error){file,err:=os.Open(path)iferr!=nil{returnnil,err}deferfile.Close()varlines[]stringscanner:=bufio.NewScanner(file)forscanner.Scan(){lines=append(lines,scanner.Text())}returnlines,scanner.Err()}funcabs(xint)int{ifx<0{return-x}returnx}funcmax(a,bint)int{ifa<b{returnb}returna}funcmin(a,bint)int{returnmax(-a,-b)}funccalculateDistance(p1,p2coord)int{returnabs(p1.x-p2.x)+abs(p1.y-p2.y)}funcmakeCoord(dstring)coord{c:=strings.Split(d,", ")x,_:=strconv.Atoi(c[1])y,_:=strconv.Atoi(c[0])returncoord{x:x,y:y}}funcmain(){data,err:=readLines("input")iferr!=nil{panic(err)}// Find the outer border:varouterTop,outerBottom,outerLeft,outerRightintouterBottom=math.MaxInt32outerLeft=math.MaxInt32P:=[]coord{}for_,d:=rangedata{c:=makeCoord(d)P=append(P,c)outerTop=max(outerTop,c.y)outerBottom=min(outerBottom,c.y)outerLeft=min(outerLeft,c.x)outerRight=max(outerRight,c.x)}// Map coordinates to closest points and count:countP:=map[coord]int{}canvas:=map[coord]int{}fory:=outerBottom;y<=outerTop;y++{forx:=outerLeft;x<=outerRight;x++{c:=coord{x:x,y:y}canvas[c]=0minDist:=math.MaxInt32varminPcoordfor_,p:=rangeP{dist:=calculateDistance(c,p)canvas[c]+=distifdist==minDist{minP=coord{x:math.MinInt32,y:math.MinInt32}}elseifdist<minDist{minP=pminDist=dist}}ifminP.x!=math.MinInt32&&minP.y!=math.MinInt32{ifcountP[minP]!=math.MinInt32{countP[minP]=countP[minP]+1}ify==outerTop||y==outerBottom||x==outerLeft||x==outerRight{countP[minP]=math.MinInt32}}}}// Part 1:fmt.Println("Part 1:")maxi:=math.MinInt32varmaxiPcoordfork,v:=rangecountP{ifv>maxi{maxi=vmaxiP=k}}fmt.Printf("Point with largest area: %v\n",maxiP)fmt.Printf("The area is: %v\n",maxi)// Part 2:fmt.Println("\nPart 2:")varregion[]coordforp,d:=rangecanvas{ifd<10000{region=append(region,p)}}fmt.Printf("Size of region: %v\n",len(region))}
I'm starting to see what's going on here. The elves of 1518 have somehow procured a future book of classic programming algorithms. Today there was a little distraction in problem description around infinite spaces and equidistant points, but it boils down to a pixel fill algorithm. If you ever worked on a classic 1980s-90s style paint program you'll recognise it.
First let's make some geometric primitives:
data classPos(valx:Int,valy:Int)data classBox(valx:IntRange,valy:IntRange)enumclassDir{UP,DOWN,LEFT,RIGHT}typealiasManhattan=Int
For readable code we'll add some useful operations, using Kotlin's carefully limited operator overloading where appropriate:
We need a data model for our coordinates, and a parser. I know it's just two integers separated by a comma, but I hope by now you see what I mean by the One True Way of parsing text:
The last two lines make an infinite sequence of integers and zip it with the parsed coordinates so each coordinate gets a "name".
The problem talks about an infinite space, but the insight you need to solve it is that any area which reaches the edge of the "known" space is effectively infinite. The known space is the extent of the input coordinates in every direction, so let's compute that:
Breaking things down into tiny operations makes everything much easier to understand, means you're composing ideas at the same level of abstraction and frankly leaves fewer places for bugs to creep in.
We know the size of the space, now we need the area filling data model. A cell is either unclaimed, has a coordinate on it, is claimed by a single coordinate or is equidistant to two or more coordinates. It's a sum type!
And we represent the space with a simple two-dimensional array of these:
data classSpace(valbox:Box){valcells:Array<Array<Cell>>init{valwidth=box.x.endInclusive-box.x.start+1valheight=box.y.endInclusive-box.y.start+1cells=Array<Array<Cell>>(height){Array<Cell>(width){Cell.Unclaimed}}}
Now the fill algorithm. I've written this before, so I'm happy to say once it compiled this actually gave the correct result first time. The only change I had to make was to change it from stack-based recursion to a heap-based stack of pending operations to cope with the size of the main input data.
Recursive functions are often best implemented with a helper and a "starter" method.
In the original version the inner fill() helper called itself recursively. Now it returns the next set of cells to fill, and the outer loop processes these.
The first check is that we've remained inside the bounding box. You could note here that the affected ID has an infinite area but I feel that's conflating the fill and area check parts of the problem. Merge them later if performance is an issue.
The when block looks at the current state of the cell and computes what to do. If the cell is unclaimed or closer to the current coordinate, we claim it. If it's the same distant it becomes Equidistant. Some of the cases leave it alone.
Finally if we've made a change to the cell, update the 2D array and continue to the surrounding set of cells.
Part 1
The question in part 1 is to determine the largest finite area. We need a sum type!
This is not the most efficient form of the algorithm by any means. It will check the same cell many times during a pass. It runs in 1.5 seconds on my Thinkpad though.
Part 2
Part 2 was a bit disappointing as it didn't really build on the first much. I refactored the Space class to be generic in its content, so I could make a space of manhattan distances. Filling it was easy:
<?php$file=fopen("input.txt","r")orexit("Unable to open file");while(!feof($file)){$array[]=explode(", ",fgets($file));}fclose($file);returnarray_filter(array_map(function($val){returnarray((int)$val[0],(int)$val[1]);},$array));?>
Oh no, I've been affected by the bug in Day 6. My solution was correct, but the adventofcode.com didn't recognise it. I tested some of the solutions posted here and they gave the same output as mine. I'm not sure how to progress to Part 2, so only posting Part 1:
#!/usr/bin/perlusewarnings;usestrict;usefeatureqw{ say };useList::Utilqw{ max };my%locations;my$location='A';my($X,$Y)=(0,0);while(<>){chomp;my($x,$y)=split/,\s+/;$locations{$location++}=[$x,$y];$X=$xif$x>$X;$Y=$yif$y>$Y;}my@nearest;formy$x(0..$X+1){formy$y(0..$Y+1){my@n=(abs($locations{A}[0]-$x)+abs($locations{A}[1]-$y),'A');formy$location(keys%locations){nextif'A'eq$location;my$distance=abs($locations{$location}[0]-$x)+abs($locations{$location}[1]-$y);if($distance<=$n[0]){@n=($distance)if$distance<$n[0];push@n,$location;}}$nearest[$x][$y]=\@n;}}my%freq;++$freq{$_->[1]}forgrep@$_==2,map @$_, @nearest;delete@freq{map $_->[1], grep @$_ == 2, $nearest[0],$nearest[-1],map($_->[0], @nearest),map($_->[-1], @nearest)};saymax(0, values%freq);
Ryan is an engineer in the Sacramento Area with a focus in Python, Ruby, and Rust. Bash/Python Exercism mentor. Coding, physics, calculus, music, woodworking. Looking for work!
The worst for this day was understanding the instructions. I just couldn't for the life of me understand what was being asked, until a friend helped me and then I was on my way.
Full disclosure: I was feeling pretty dumb, and almost gave up. I'm glad I didn't 🙏
importFsfrom"fs"importPathfrom"path"constinput=Fs.readFileSync(Path.join(__dirname,"input.txt")).toString().trim().split("\n")interfaceCoordinates{x:numbery:number}typeDistance=numbertypeBoundaries=[Coordinates,Coordinates]typeID=stringtypePixel=null|IDfunctiongetIdFromCoordinates(coords:Coordinates):ID{return`${coords.x}${coords.y}`}functionconvertToCoordinates(line:string):Coordinates{const[x,y]=line.split(", ")return{x:parseInt(x,10),y:parseInt(y,10)}}functioncomputeManhattanDistance(a:Coordinates,b:Coordinates):Distance{returnMath.abs(a.x-b.x)+Math.abs(a.y-b.y)}functioncomputeBoardBoundaries(coords:Array<Coordinates>):Boundaries{returncoords.reduce((boundaries:Boundaries,coordinate:Coordinates,index:number)=>{let[topLeftBoundary,bottomRightBoundary]=boundariesif(index===0){return[{...coordinate},{...coordinate}]asBoundaries}if(coordinate.x<topLeftBoundary.x){topLeftBoundary.x=coordinate.x}if(coordinate.y<topLeftBoundary.y){topLeftBoundary.y=coordinate.y}if(coordinate.x>bottomRightBoundary.x){bottomRightBoundary.x=coordinate.x}if(coordinate.y>bottomRightBoundary.y){bottomRightBoundary.y=coordinate.y}return[topLeftBoundary,bottomRightBoundary]asBoundaries},[// first element is top left boundary{x:0,y:0},// last element is bottom right boundary{x:0,y:0},]asBoundaries)}functiondoesBeaconHaveFiniteArea(beacon:Coordinates,beacons:Array<Coordinates>):Boolean{lethasTopLeft=falselethasTopRight=falselethasBottomLeft=falselethasBottomRight=falsefor(leti=0;i<beacons.length;i++){constcomparedBeacon=beacons[i]if(hasTopRight===false&&comparedBeacon.x>=beacon.x&&comparedBeacon.y>beacon.y){hasTopRight=true}if(hasBottomRight===false&&comparedBeacon.x>=beacon.x&&comparedBeacon.y<beacon.y){hasBottomRight=true}if(hasBottomLeft===false&&comparedBeacon.x<=beacon.x&&comparedBeacon.y<beacon.y){hasBottomLeft=true}if(hasTopLeft===false&&comparedBeacon.x<=beacon.x&&comparedBeacon.y>beacon.y){hasTopLeft=true}}returnhasTopLeft&&hasTopRight&&hasBottomLeft&&hasBottomRight}constcoordinates=input.map(convertToCoordinates)constbeaconsWithFiniteArea:Array<Coordinates>=coordinates.reduce((beacons,beacon)=>{consthasFiniteArea=doesBeaconHaveFiniteArea(beacon,coordinates.filter(ref=>ref.x!==beacon.x||ref.y!==beacon.y))if(hasFiniteArea){return[...beacons,beacon]}returnbeacons},[]asArray<Coordinates>)constboundaries:Boundaries=computeBoardBoundaries(coordinates)console.log(JSON.stringify(boundaries))functioncomputePixelForCoords(coordinates:Coordinates,coordinatesList:Array<Coordinates>):Pixel{constdistances=coordinatesList.map(beacon=>{return{id:getIdFromCoordinates(beacon),distance:computeManhattanDistance(coordinates,beacon),}}).sort((a,b)=>a.distance-b.distance)if(distances[0].distance===distances[1].distance){returnnull}returndistances[0].id}constcanvas:Array<Array<Pixel>>=[]for(letx=boundaries[0].x;x<=boundaries[1].x;x++){for(lety=boundaries[0].y;y<=boundaries[1].y;y++){if(canvas[x]===undefined){canvas[x]=[]}canvas[x][y]=computePixelForCoords({x,y},coordinates)}}constsorted=beaconsWithFiniteArea.map(beacon=>{letcount=0letid=getIdFromCoordinates(beacon)for(letx=boundaries[0].x;x<=boundaries[1].x;x++){for(lety=boundaries[0].y;y<=boundaries[1].y;y++){if(canvas[x][y]===id){count++}}}returncount}).sort((a,b)=>b-a)// 01console.log(sorted[0])
Part2:
importFsfrom"fs"importPathfrom"path"constinput=Fs.readFileSync(Path.join(__dirname,"input.txt")).toString().trim().split("\n")interfaceCoordinates{x:numbery:number}typeDistance=numbertypeBoundaries=[Coordinates,Coordinates]functionconvertToCoordinates(line:string):Coordinates{const[x,y]=line.split(", ")return{x:parseInt(x,10),y:parseInt(y,10)}}functioncomputeManhattanDistance(a:Coordinates,b:Coordinates):Distance{returnMath.abs(a.x-b.x)+Math.abs(a.y-b.y)}functioncomputeBoardBoundaries(coords:Array<Coordinates>):Boundaries{returncoords.reduce((boundaries:Boundaries,coordinate:Coordinates,index:number)=>{let[topLeftBoundary,bottomRightBoundary]=boundariesif(index===0){return[{...coordinate},{...coordinate}]asBoundaries}if(coordinate.x<topLeftBoundary.x){topLeftBoundary.x=coordinate.x}if(coordinate.y<topLeftBoundary.y){topLeftBoundary.y=coordinate.y}if(coordinate.x>bottomRightBoundary.x){bottomRightBoundary.x=coordinate.x}if(coordinate.y>bottomRightBoundary.y){bottomRightBoundary.y=coordinate.y}return[topLeftBoundary,bottomRightBoundary]asBoundaries},[// first element is top left boundary{x:0,y:0},// last element is bottom right boundary{x:0,y:0},]asBoundaries)}constcoordinates=input.map(convertToCoordinates)constboundaries:Boundaries=computeBoardBoundaries(coordinates)constMAX_DISTANCE=10000letclosestSum:number=-Infinityletposition:Coordinates={x:Infinity,y:Infinity}letnumberOfPixelsForRegion:number=0for(letx=boundaries[0].x;x<=boundaries[1].x;x++){for(lety=boundaries[0].y;y<=boundaries[1].y;y++){constbeacon={x,y}constsum=coordinates.reduce((total,point)=>{returntotal+computeManhattanDistance(beacon,point)},0)if(sum<MAX_DISTANCE){numberOfPixelsForRegion++if(sum>closestSum){closestSum=sumposition={x,y}}}}}console.log(`Chosen position ${JSON.stringify(position)} with sum ${closestSum} and region size: ${numberOfPixelsForRegion}`)
Ryan is an engineer in the Sacramento Area with a focus in Python, Ruby, and Rust. Bash/Python Exercism mentor. Coding, physics, calculus, music, woodworking. Looking for work!
I want to come back to this exercise after Advent is over and refactor, because I think I could make my solution really nice now, but for right now, I'm delirious and need sleep. Here's what I've got. It's not pretty, but it gets stars and that's all that matters.
/// Day 6: Chronal Coordinates/// /// Calculate Manhattan Distances on the X-Y planeusestd::collections::HashMap;usestd::collections::HashSet;/// A grid of X-Y coordinates and unclaimed points/// /// coords is a vector of Coordinates. Their index is their "ID number"/// points is a vector of unclaimed points. Their value is the ID of the /// closest CoordinatestructGrid{coords:Vec<Coordinate>,points:Vec<Option<usize>>,width:usize,height:usize,}implGrid{pubfnnew()->Self{Self{coords:vec![],points:vec![],width:0,height:0}}/// Loads a grid from text, building each coordinate and calculating/// most of part 1pubfnfrom_text(text:&str)->Self{letmutgrid=Grid::new();forlineintext.lines(){letmutcoord=Coordinate::from_str(line);coord.id=grid.coords.len();grid.coords.push(coord);}let(height,width)=grid.bounds();grid.height=height;grid.width=width;grid.points.resize(width*height,None);grid.calculate_closest_coords();grid}/// Calculates the width and height of the gridfnbounds(&self)->(usize,usize){letmax_row=self.coords.iter().map(|coord|coord.y).max().unwrap();letmax_col=self.coords.iter().map(|coord|coord.x).max().unwrap();(max_row,max_col)}/// For each point on the Grid, calculates the closest Coordinate to/// that point/// /// Ties count as nothing!fncalculate_closest_coords(&mutself){foryin0..self.height{forxin0..self.width{letmutmin_dist=self.width+self.height;forcoordinself.coords.iter(){letdist=coord.manhattan_distance_to(x+1,y+1);ifdist<min_dist{min_dist=dist;self.points[x+y*self.width]=Some(coord.id);}elseifdist==min_dist{// It's a tie. No one gets itself.points[x+y*self.width]=None;}}}}}/// Checks whether or not a coordinate is internal/// /// Internal coordinates are completely fenced in by other/// coordinates. No infinite boundaries (i.e. not touching the edges)/// /// This could probably be batched in the contructor rather than done in a loop.fnis_internal(&self,id:usize)->bool{letmutexternal:HashSet<usize>=HashSet::new();// Left and right sideforyin0..self.height{letleft=self.points[0+y*self.width];letright=self.points[y*self.width+self.width-1];ifleft.is_some(){external.insert(left.unwrap());}ifright.is_some(){external.insert(right.unwrap());}}// Top and bottomforxin0..self.width{lettop=self.points[x];letbottom=self.points[x+(self.height-1)*self.width];iftop.is_some(){external.insert(top.unwrap());}ifbottom.is_some(){external.insert(bottom.unwrap());}}!external.contains(&id)}/// Calculates the area of the internal coordinate that claims the most areapubfnmost_claimed_area(&self)->usize{letmutcounter:HashMap<usize,usize>=HashMap::new();forpointinself.points.iter(){ifpoint.is_some(){*counter.entry(point.unwrap()).or_insert(0)+=1;}}*counter.iter().filter(|(id,_count)|self.is_internal(**id)).map(|(_id,count)|count).max().unwrap()}/// Counts how many points have a total manhattan distance less than/// a threshold when checked against all Coordinatespubfnsquares_closer_than(&self,dist:usize)->usize{letmutdistances:Vec<usize>=vec![];foryin0..self.height{forxin0..self.width{lettotal=self.coords.iter().fold(0,|acc,coord|acc+coord.manhattan_distance_to(x,y));iftotal<dist{distances.push(total);}}}distances.iter().count()}}/// An X-Y coordinate on a GridstructCoordinate{id:usize,x:usize,y:usize,}implCoordinate{pubfnnew(x:usize,y:usize)->Self{Self{id:0,x,y}}/// Loads data from a line of text, essentially a CSV linepubfnfrom_str(text:&str)->Self{letmutparts=text.split(',');letx=parts.next().unwrap().trim().parse().unwrap();lety=parts.next().unwrap().trim().parse().unwrap();Self{id:0,x,y}}/// Calculate manhattan distance from here to any X-Y pairpubfnmanhattan_distance_to(&self,x:usize,y:usize)->usize{letx1=self.xasi32;letx2=xasi32;lety1=self.yasi32;lety2=yasi32;((x2-x1).abs()+(y2-y1).abs())asusize}}/// Part 1pubfnlargest_finite_area(text:&str)->usize{letgrid=Grid::from_text(text);grid.most_claimed_area()}/// Part 2pubfnsquares_closer_than(text:&str,dist:usize)->usize{letgrid=Grid::from_text(text);grid.squares_closer_than(dist)}
never met a part of the stack I didn't like. sr. engineer at clique studios in chicago, perpetual creative hobbyist, bird friend, local gay agenda promoter. she/her. tips: https://ko-fi.com/carlymho
Weird fact I learned about PHP in this puzzle: if you try and splice a very large array into an already very large multidimensional array, at a certain point PHP will throw an overflow error, but it doesn't happen with array_pad?
Anyway for the first one, I ended up just manually increasing the value by which I was expanding the grid and seeing at what point the number I was getting stopped changing, since "is this infinite" is hard to check for. The second part, I checked all the edge spaces to see if any of them were part of the central area, and if they weren't then I knew I could stop.
I doubt this is the most performant solution, but it gets the job done for smaller sets of input data.
-- Set up some stufflocalinf=math.abs(1/0)localpoint={__tostring=function(self)returnstring.format("(%03i, %03i)",self[1],self[2])end}-- First of all, read in all the points.-- This is just some boring I/O, and integer parsing.localpoints={}forlineinio.stdin:lines()dolocalpoint=setmetatable({},point)forcoordinateinline:gmatch('%d+')dotable.insert(point,tonumber(coordinate))endtable.insert(points,point)end-- Find smallest axis-aligned rectangle (R) that contains all pointslocalmin_x,max_x,min_y,max_y=inf,0,inf,0for_,pointinipairs(points)dolocalx,y=point[1],point[2]min_x=math.min(x,min_x)min_y=math.min(y,min_y)max_x=math.max(x,max_x)max_y=math.max(y,max_y)end-- Helper function that finds the nearest input point given two coordinates.localfunctionnearest(x,y)localdist,point=inflocalmid=falsefor_,pinipairs(points)dolocald=(math.abs(x-p[1])+math.abs(y-p[2]))ifd==distthenmid=trueelseifd<distthenmid=falsedist=dpoint=pendendreturn(notmid)andpointend-- Mapping points to their respective counts:-- Iterate all points in R, find the nearest input point and add 1 to its count.localcounts={}fory=min_y,max_ydoforx=min_x,max_xdolocalnearest=nearest(x,y)ifnearestthenify==min_yory==max_yorx==max_xorx==min_xthencounts[nearest]=-infelsecounts[nearest]=(counts[nearest]or0)+1endendendend-- Iterate all point-count pairs and remember the highest count; this is our answer.localhighest=0forpoint,countinpairs(counts)doprint(point,count)highest=math.max(count,highest)end-- Output the answer and be happy about it :)print(highest)
The idea for part one is to first determine the maximum field, based on the min/max values of the x and y coordinates. Then, for each position in the field, determine the distance to each of the input coordinates, keeping track of the coordinate that is the closest to the position in the field. Once this information is calculated, we can determine the size of the largest area within the field.
Part two largely follows the same logic, but takes the sum of a position in the field to all input coordinates, instead of taking only the closest one.
I enjoyed doing this one. It somehow felt satisfying. Maybe it's because for this one I was able to realize it's a series of steps and I could develop and test each one of them step-by-step, so I felt confident I was moving into the right direction.
const{readFile}=require('./reader');const{Coordinate,buildCoordinates,getGridDimension,plotCoordinates,calculateManhattanDistance}=require('./06-common');constplotLocations=(coordinates,grid)=>{constn=grid.length;for(leti=0;i<n;i++){for(letj=0;j<n;j++){if(!grid[i][j]){constlocation=newCoordinate(i,j);grid[i][j]=location;letsmallestDistance;letclosestCoordinates;for(letcoordinateofcoordinates){constdistance=calculateManhattanDistance(location,coordinate);if(!closestCoordinates||distance<smallestDistance){smallestDistance=distance;closestCoordinates=[coordinate];}elseif(distance===smallestDistance){closestCoordinates.push(coordinate);}}if(closestCoordinates.length===1){location.closestCoordinate=closestCoordinates[0];}}}}};constgetFiniteCoordinateIds=(coordinates,grid)=>{constn=grid.length;constinfiniteCoordinateIds=newSet();// Top and bottom edgesfor(leti=0;i<n;i+=n-1){for(letj=0;j<n;j++){constclosestCoordinate=grid[i][j].closestCoordinate;if(closestCoordinate){infiniteCoordinateIds.add(closestCoordinate.id);}}}// Left and right edgesfor(letj=0;j<n;j+=n-1){for(leti=0;i<n;i++){constclosestCoordinate=grid[i][j].closestCoordinate;if(closestCoordinate){infiniteCoordinateIds.add(closestCoordinate.id);}}}constfiniteCoordinateIds=coordinates.filter(coordinate=>!infiniteCoordinateIds.has(coordinate.id)).map(coordinate=>coordinate.id);returnnewSet(finiteCoordinateIds);};constcalculateCoordinateRegions=(finiteCoordinates,grid)=>{constn=grid.length;constfrequencyMap=newMap();for(leti=0;i<n;i++){for(letj=0;j<n;j++){constclosestCoordinate=grid[i][j].closestCoordinate;if(closestCoordinate&&finiteCoordinates.has(closestCoordinate.id)){const{id}=closestCoordinate;constfrequency=frequencyMap.get(id)||1;frequencyMap.set(id,frequency+1);}}}returnfrequencyMap;};(async()=>{constlines=awaitreadFile('06-input.txt');constcoordinates=buildCoordinates(lines);constgrid=plotCoordinates(coordinates);plotLocations(coordinates,grid);constfiniteCoordinateIds=getFiniteCoordinateIds(coordinates,grid);constcoordinateRegions=calculateCoordinateRegions(finiteCoordinateIds,grid);constlargestRegion=Math.max(...coordinateRegions.values());console.log(`The largest region is ${largestRegion}`);})();
06b.js
const{readFile}=require('./reader');const{Coordinate,buildCoordinates,getGridDimension,plotCoordinates,calculateManhattanDistance}=require('./06-common');constMAX_TOTAL_DISTANCE=10000;constplotLocations=(coordinates,grid)=>{constn=grid.length;for(leti=0;i<n;i++){for(letj=0;j<n;j++){letlocation=grid[i][j];if(!grid[i][j]){location=newCoordinate(i,j);grid[i][j]=location;}consttotalDistance=coordinates.reduce((sum,coordinate)=>{returnsum+calculateManhattanDistance(location,coordinate);},0);location.inSafeRegion=totalDistance<MAX_TOTAL_DISTANCE;}}};constgetSafeRegionSize=grid=>{constn=grid.length;letsafeRegionSize=0;for(leti=0;i<n;i++){for(letj=0;j<n;j++){constlocation=grid[i][j];safeRegionSize+=+location.inSafeRegion;}}returnsafeRegionSize;};(async()=>{constlines=awaitreadFile('06-input.txt');constcoordinates=buildCoordinates(lines);constgrid=plotCoordinates(coordinates);plotLocations(coordinates,grid);constsafeRegionSize=getSafeRegionSize(grid);console.log(`The safe region size is ${safeRegionSize}`);})();
Hi! I'm back again with a cool visual. There area lot of visuals out there with proper colors and cool stuff with how the entire matrix was distributed, I just wanted to highlight the points that are at a same distance from multiple 'origins'.
Also, if you guys are still looking for answers and guides I'm hosting a beginner friendly solution guide on my repo
Conclusion: Manhattan distance makes cool visuals
For further actions, you may consider blocking this person and/or reporting abuse
We're a blogging-forward open source social network where we learn from one another
Here is a quick and dirty (and slow!) Python solution. I will later translate it to Golang, but I doubt it will become any faster since it already uses dictionaries (hashmaps). What slows it down is probably iterating through each coordinate individually.
Python using a kd-tree.
Here is the Golang code. Even though it uses the exact same algorithm, it runs surprisingly faster!
From my github:
I'm starting to see what's going on here. The elves of 1518 have somehow procured a future book of classic programming algorithms. Today there was a little distraction in problem description around infinite spaces and equidistant points, but it boils down to a pixel fill algorithm. If you ever worked on a classic 1980s-90s style paint program you'll recognise it.
First let's make some geometric primitives:
For readable code we'll add some useful operations, using Kotlin's carefully limited operator overloading where appropriate:
We need a data model for our coordinates, and a parser. I know it's just two integers separated by a comma, but I hope by now you see what I mean by the One True Way of parsing text:
The last two lines make an infinite sequence of integers and zip it with the parsed coordinates so each coordinate gets a "name".
The problem talks about an infinite space, but the insight you need to solve it is that any area which reaches the edge of the "known" space is effectively infinite. The known space is the extent of the input coordinates in every direction, so let's compute that:
Breaking things down into tiny operations makes everything much easier to understand, means you're composing ideas at the same level of abstraction and frankly leaves fewer places for bugs to creep in.
We know the size of the space, now we need the area filling data model. A cell is either unclaimed, has a coordinate on it, is claimed by a single coordinate or is equidistant to two or more coordinates. It's a sum type!
And we represent the space with a simple two-dimensional array of these:
Now the fill algorithm. I've written this before, so I'm happy to say once it compiled this actually gave the correct result first time. The only change I had to make was to change it from stack-based recursion to a heap-based stack of pending operations to cope with the size of the main input data.
Things to note:
fill()
helper called itself recursively. Now it returns the next set of cells to fill, and the outer loop processes these.when
block looks at the current state of the cell and computes what to do. If the cell is unclaimed or closer to the current coordinate, we claim it. If it's the same distant it becomesEquidistant
. Some of the cases leave it alone.Part 1
The question in part 1 is to determine the largest finite area. We need a sum type!
To calculate the area for a coordinate ID I took a very simple approach:
This is not the most efficient form of the algorithm by any means. It will check the same cell many times during a pass. It runs in 1.5 seconds on my Thinkpad though.
Part 2
Part 2 was a bit disappointing as it didn't really build on the first much. I refactored the
Space
class to be generic in its content, so I could make a space of manhattan distances. Filling it was easy:Then the size of the area under the threshold is
space.count { d -> d < max }
I enjoyed this one a lot, which was pleasing after yesterday. Roll on the next...
PHP solution
Part 1 took me a while to figure out how to get rid of coordinates that have infinite area. Part 2 was straight forward loop.
Btw using
array_filter
was ~3x slower thanforeach
loop. PHP ¯\(ツ)/¯Part 1
Part2
readFile.php
Kotlin Solution
Part 1
This one wasn't too bad. I had a false start trying to flood-fill with multiple generators, and that got silly really quickly.
Then I realized I could just build a big array, chunk through item by item, remove any symbol on an edge, and then just count the places they reached.
Part 2
This is where I got stuck a bit trying to optimize instead of just letting it finish this code's 30 second cycle.
I'm not happy I couldn't figure out the math, but I'll post some pictures I've generated from my output in penance.
The promised images!
Sample Data
Part 1 - Answer

Part 2 - Answer

Answers
Part 1 - Answer

Part 2 - Answer

Annnnnd, by posting these pictures, I see that the optimization for part 2 is to bound yourself by the points. 10000 is a bit of a red herring.
EDIT: 2018-12-06T11:01:40-08:00
Found some problems with my image processing and fixed them.
You don't have to construct an array; you can just iterate over min(x)..max(x) and min(y)..max(y) and add + to the nearest input point.
Oh no, I've been affected by the bug in Day 6. My solution was correct, but the adventofcode.com didn't recognise it. I tested some of the solutions posted here and they gave the same output as mine. I'm not sure how to progress to Part 2, so only posting Part 1:
Oh no! That’s a bummer 😐
The worst for this day was understanding the instructions.
I just couldn't for the life of me understand what was being asked, until a friend helped me and then I was on my way.
Full disclosure: I was feeling pretty dumb, and almost gave up. I'm glad I didn't 🙏
Part2:
I want to come back to this exercise after Advent is over and refactor, because I think I could make my solution really nice now, but for right now, I'm delirious and need sleep. Here's what I've got. It's not pretty, but it gets stars and that's all that matters.
PHP
Weird fact I learned about PHP in this puzzle: if you try and splice a very large array into an already very large multidimensional array, at a certain point PHP will throw an overflow error, but it doesn't happen with array_pad?
Anyway for the first one, I ended up just manually increasing the value by which I was expanding the grid and seeing at what point the number I was getting stopped changing, since "is this infinite" is hard to check for. The second part, I checked all the edge spaces to see if any of them were part of the central area, and if they weren't then I knew I could stop.
Part 1:
Part 2:
This is my least favorite code I may have ever written. Need to refactor.
Part 1
part 2
I doubt this is the most performant solution, but it gets the job done for smaller sets of input data.
Here we go, this is my solution in Elixir.
The idea for part one is to first determine the maximum field, based on the min/max values of the x and y coordinates. Then, for each position in the field, determine the distance to each of the input coordinates, keeping track of the coordinate that is the closest to the position in the field. Once this information is calculated, we can determine the size of the largest area within the field.
Part two largely follows the same logic, but takes the sum of a position in the field to all input coordinates, instead of taking only the closest one.
Common:
Part one:
Part two:
Javascript solution
I enjoyed doing this one. It somehow felt satisfying. Maybe it's because for this one I was able to realize it's a series of steps and I could develop and test each one of them step-by-step, so I felt confident I was moving into the right direction.
reader.js
06-common.js
06a.js
06b.js
Better late than never...
Had no time yesterday to write the solution down, but here it is.
Most of the time I struggled to understand how to filter "infinity areas". After I solved this it was a pretty easy.
A Visual
Hi! I'm back again with a cool visual. There area lot of visuals out there with proper colors and cool stuff with how the entire matrix was distributed, I just wanted to highlight the points that are at a same distance from multiple 'origins'.
Also, if you guys are still looking for answers and guides I'm hosting a beginner friendly solution guide on my repo
Conclusion: Manhattan distance makes cool visuals