I decided to create a tree to represent each Step, adding dependencies and dependents for each steap, and a sorting algorithm to sort steps about to processed (i.e. when it has no dependents).
Then, I use a BFS traversal, employing a queue to visit the steps. In the 1st part, if a step has been processed but still has dependents, I re-add it to the end queue. For the 2nd part, I don't need this because then it won't be picked up by another worker, it remains with the same worker for the rest of the duration of that step.
const{readFile}=require('./reader');const{Step,getStep,buildSteps,getFirstSteps,sortSimilarSteps}=require('./07-common');constgetStepsOrder=steps=>{letstepsOrder='';while(steps.length>0){conststep=steps.shift();stepsOrder+=step.letter;for(letdependentofstep.dependents){constindex=dependent.dependencies.indexOf(step);dependent.dependencies.splice(index,1);if(dependent.dependencies.length===0){steps.push(dependent);}}sortSimilarSteps(steps);}returnstepsOrder;};(async()=>{constlines=awaitreadFile('07-input.txt');conststeps=buildSteps(lines);constfirstSteps=getFirstSteps(steps);conststepsOrder=getStepsOrder(firstSteps);console.log(`The steps order is ${stepsOrder}`);})();
07b.js
const{readFile}=require('./reader');const{Step,getStep,buildSteps,getFirstSteps,sortSimilarSteps}=require('./07-common');constWARM_UP=60;constWORKERS=15;constgetStepDuration=step=>WARM_UP+step.letter.charCodeAt(0)-64;constgetStepsTime=steps=>{constworkers=Array.from({length:WORKERS},w=>newObject({step:null}));letisThereWorkLeft=steps.length>0;letstepsOrder='';lettotalSeconds=0;while(isThereWorkLeft){for(letworkerofworkers){letstep=worker.step;// If worker is done, go over dependents and get yourself a new stepif(step&&step.elapsedSeconds===getStepDuration(step)){stepsOrder+=step.letter;for(letdependentofstep.dependents){constindex=dependent.dependencies.indexOf(step);dependent.dependencies.splice(index,1);if(dependent.dependencies.length===0){steps.push(dependent);}}sortSimilarSteps(steps);worker.step=null;step=null;}// Assigning new stepif(!step){step=steps.shift();if(!step){continue;}worker.step=step;step.worker=worker;step.elapsedSeconds=0;}if(step.elapsedSeconds<getStepDuration(step)){step.elapsedSeconds++;}}isThereWorkLeft=!!workers.find(w=>!!w.step);if(isThereWorkLeft){totalSeconds++;}}return{stepsOrder,totalSeconds};};(async()=>{constlines=awaitreadFile('07-input.txt');conststeps=buildSteps(lines);constfirstSteps=getFirstSteps(steps);const{totalSeconds}=getStepsTime(firstSteps);console.log(`The steps time is ${totalSeconds}`);})();
Today I could figure out only the first part :( Wasted quite a bit of time because example input and real input had one big difference between data structures (and I assumed wrongly ;).
<?php$file=fopen("input.txt","r")orexit("Unable to open file");// $file = fopen("input_test.txt", "r") or exit("Unable to open file");while(!feof($file)){$array[]=fgets($file);}fclose($file);returnarray_filter($array);?>
Yeah, I wish the sample was more representative of the actual data. I struggled with getting this part 1 done, and was not ready for part 2, to many differences and I had already spent more time on day 7 than any other day
Notes: If you see US in the code, that's a quick kotlinization of the __ groovy dsl, which doesn't play nicely with IntelliJ.
Part 2
Ok, now that I had a method for stepping through the graph, it was much easier to add resources and have time get updated in the DB as I traversed and deleted nodes.
At least, it would have been if I remembered how to gremlin. I spent more time than I'm willing to admit relearning my group() and order() semantics. Then I spent some time trying to make sure that my traversal prioritized items that were already started, as the sample gave different answers if your workers changed steps willy-nilly.
Post-submission optimization: you can just step down the minimum time of the nodes being worked on instead of 1.
This is my C# solution. I'm using Advent of Code this year to practice C# so I'm sure there's lots of opportunity for improvement, but it works! I only work on these in the half hour between getting to work and my workday starting (don't ask) and sometimes at lunch, so I might fall pretty far behind after this weekend...
I quite liked this puzzle! It took me a few false starts-- I couldn't decide whether to track a step's dependencies or dependents --but ultimately I quite like the way my code turned out. Calling sort on every step seemed...wasteful...to me, but it was fast enough, so it all worked out in the end.
Here's my Julia code (boring parsing, etc. omitted but available here)
function step_ordering(input)step_map=parse_input(input)result=alpha_bfs(step_map)returnresultendfunction alpha_bfs(step_map)roots=find_roots(step_map)result=""seen=Set()while(!isempty(roots))sort!(roots)found=popfirst!(roots)result*=foundforotherinvalues(step_map)delete!(other.prev,found)ifisempty(other.prev)&&!(other.nameinseen)push!(seen,other.name)push!(roots,other.name)endendendreturnresultendfunction find_roots(step_map)all_nodes=Set{String}()fornodeinvalues(step_map)forprevinnode.prevpush!(all_nodes,prev)endendreturncollect(setdiff(all_nodes,keys(step_map)))end
Part 2 was also enjoyable-- I briefly considered actually spawning 5 worker threads and keeping a global clock, but then I realized I could just track how much work was being done in between tracking which nodes are now available.
function costed_alpha_bfs(step_map)roots=find_roots(step_map)result=""seen=Set()total_time=0costs=Dict{String,Int}()base_cost=60max_workers=5workers=max_workerswhile(!isempty(roots)||!isempty(costs))sort!(roots)whileworkers>0&&!isempty(roots)found=popfirst!(roots)costs[found]=cost_of_node(found)+base_costworkers-=1end(cost,found)=findmin(costs)result*=foundtotal_time+=costworkers=min(max_workers,workers+1)delete!(costs,found)forin_progressinkeys(costs)costs[in_progress]-=costendforotherinvalues(step_map)delete!(other.prev,found)ifisempty(other.prev)&&!(other.nameinseen)push!(seen,other.name)push!(roots,other.name)endendendreturntotal_timeend
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
ohhhhh my god this one took me a really long time for a variety of goofy reasons, mostly not understanding how things were supposed to work correctly
Part 1:
<?php$input=file_get_contents($argv[1]);$instructions=explode("\n",trim($input));$steplist=str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ');$steps=array();$after=array();foreach($steplistas$s){$after[$s]=array();}foreach($instructionsas$inst){preg_match('/Step ([A-Z]) must be finished before step ([A-Z]) can begin./',$inst,$stepnames);array_push($after[$stepnames[2]],$stepnames[1]);}foreach($steplistas$i=>$step){if(count($after[$step])==0){array_push($steps,$step);unset($after[$step]);while(count($after)){$next_candidates=array_filter($after,function($v,$k){global$steps;if(array_intersect($v,$steps)==$v){returntrue;}returnfalse;},ARRAY_FILTER_USE_BOTH);ksort($next_candidates);if($next_candidates){$next=array_keys($next_candidates)[0];array_push($steps,$next);unset($after[$next]);}else{break;}}break;}}echojoin("",$steps);die(1);
Part 2:
<?php$input=file_get_contents($argv[1]);$instructions=explode("\n",trim($input));$steplist=str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ');$times=array_combine($steplist,array_map(function($x){return$x+61;},array_flip($steplist)));$steps=array();$after=array();foreach($steplistas$s){$after[$s]=array();}foreach($instructionsas$inst){preg_match('/Step ([A-Z]) must be finished before step ([A-Z]) can begin./',$inst,$stepnames);array_push($after[$stepnames[2]],$stepnames[1]);}$completed=array();$workers=array_fill(0,5,array('step'=>null,'start_time'=>0));$time=0;$next_candidates=array_filter($after,function($v,$k){if(count($v)==0){returntrue;}returnfalse;},ARRAY_FILTER_USE_BOTH);ksort($next_candidates);if($next_candidates){foreach(array_keys($next_candidates)as$n){foreach($workersas$i=>$worker){if(!$worker['step']){echo"assigning ".$n." to worker ".($i+1)." to start at ".$time."\n";$workers[$i]['step']=$n;$workers[$i]['start_time']=$time;unset($after[$n]);break;}}}}while(count($completed)<count($steplist)){$availableWorkers=0;foreach($workersas$i=>$w){if($w['step']&&$times[$w['step']]+$w['start_time']-1==$time){array_push($completed,$w['step']);echo"Step ".$w['step']." completed by worker ".($i+1)." at ".$time." seconds\n";$workers[$i]['step']=null;$availableWorkers++;echojoin("",$completed)."\n";}}$time++;if(count($completed)==count($steplist)){break;}if(count($completed)>0&&$availableWorkers>0){$next_candidates=array_filter($after,function($v,$k){global$completed;if(array_intersect($v,$completed)==$v){returntrue;}returnfalse;},ARRAY_FILTER_USE_BOTH);ksort($next_candidates);if($next_candidates){foreach(array_keys($next_candidates)as$n){foreach($workersas$i=>$worker){if(!$worker['step']){echo"assigning ".$n." to worker ".($i+1)." to start at ".$time."\n";$workers[$i]['step']=$n;$workers[$i]['start_time']=$time;unset($after[$n]);break;}}}}}}echo$time;die(1);
The last part was difficult for me! Took me some time to figure out the small details to take care of.
For now here is my Python code. I am sure it can be simplified by a lot and made much more easy to read. Hopefully I will find time to do it later.
I am building a tree to solve this problem.
importcopywithopen('input')asf:data=f.readlines()classNode:def__init__(self,name,parents,children):self.name=nameself.parents=parentsself.parents_copy=parentsself.children=childrendef__repr__(self):parents=[p.nameforpinself.parents]ifself.parentselseNonechildren=[c.nameforcinself.children]ifself.childrenelseNonereturn'Node(name={}, parents={}, children={})'.format(self.name,parents,children)defadd_parent(self,parent):ifself.parents:self.parents.append(parent)else:self.parents=[parent]self.parents_copy=self.parents[:]defremove_parent(self,parent):ifself.parents_copy:self.parents_copy.remove(parent)defadd_child(self,child):ifself.children:self.children.append(child)else:self.children=[child]defis_root(self):returnself.parents_copy==[]nodes={}fordindata:node=nodes.get(d[5],Node(d[5],[],[]))child=nodes.get(d[36],Node(d[36],[],[]))node.add_child(child)child.add_parent(node)nodes[d[5]]=nodenodes[d[36]]=childnodes_bak=copy.deepcopy(nodes)# Part 1: # Traverse the tree: path=[]job_times={}whilelen(nodes):# Find roots: roots=[nforninnodes.values()ifn.parents_copy==[]]first_root=min(roots,key=lambdan:n.name)# Execute first_node and remove it as parent from its children: forcinfirst_root.children:c.remove_parent(first_root)# Remove node from global nodes list: delnodes[first_root.name]path.append(first_root.name)print('Part 1:')print(''.join(path))# Part 2: # Traverse the tree: nodes=nodes_bakpath=[]worker_times={'0':0,'1':0,'2':0,'3':0,'4':0}job_times={}whilelen(nodes):# Find roots: roots=[nforninnodes.values()ifn.parents_copy==[]]# Select first_root by earliest time to add: min_time=float('inf')first_root=roots[0]forrinroots:# Time for all parents to have finished iflen(r.parents)>=2:max_parent_time=max(job_times.get(p.name,0)forpinr.parents)eliflen(r.parents)==1:max_parent_time=job_times.get(r.parents[0].name,0)else:max_parent_time=0ifmax_parent_time<=min_time:ifmax_parent_time==min_time:first_root=min(first_root,r,key=lambdan:n.name)else:first_root=rmin_time=max_parent_time# Select the worker that finishes earliest: earliest_worker=min(worker_times,key=worker_times.get)worker_times[earliest_worker]=(max(min_time,worker_times[earliest_worker])+ord(first_root.name)-4# Converts ASCII to decimal with time adjustment )# Adding total time it took for job to finish: job_times[first_root.name]=worker_times[earliest_worker]# Execute first_node and remove it as parent from its children: forcinfirst_root.children:c.remove_parent(first_root)# Remove node from global nodes list: delnodes[first_root.name]print('Part 2:')print(max(worker_times.values()))
Once I got there, I found part 2 to be a fairly straightforward extension to parallelise the work and keep track of remaining time. One trick was to make the Work ordering put in-progress work first, so on each iterations the workers pulled active steps from the stack first.
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!
Got my Rust solution done! After banging my head on it all yesterday, I'm glad I took a break and came back fresh. It seemed to go pretty smoothly the second try at it.
Part 1
/// Day 7: Sum of Its Parts/// /// Unravel the order of instructions with dependenciesusestd::collections::{HashMap,HashSet};usestd::cmp;// Part 1: In what order should the steps be completed?structDependencyGraph{instructions:HashMap<char,Vec<char>>,}implDependencyGraph{pubfnnew()->Self{Self{instructions:HashMap::new()}}pubfnfrom_instructions(text:&str)->Self{letmutdeps=DependencyGraph::new();forlineintext.lines(){letparent=line.chars().take(6).last().unwrap();letchild=line.chars().take(37).last().unwrap();deps.add_dependency(parent,child);}deps}pubfnadd_dependency(&mutself,parent:char,child:char){self.instructions.entry(parent).or_insert(vec![]);letchild_deps=self.instructions.entry(child).or_insert(vec![]);child_deps.push(parent);}pubfnlinearize(&self)->Vec<char>{letmutresults:Vec<char>=vec![];letmutpending:HashSet<char>=self.instructions.keys().cloned().collect();whilepending.len()>0{letmutsatisfied:Vec<char>=self.instructions.iter().filter(|(c,deps)|{pending.contains(c)&&deps.iter().all(|dep|!pending.contains(dep))}).map(|(c,deps)|c.clone()).collect();satisfied.sort();results.push(satisfied[0]);pending.remove(&satisfied[0]);}results}}/// Given lines of dependencies, processes those dependencies into a linear/// ordered string of instructions.pubfnorder_steps(text:&str)->String{letdeps=DependencyGraph::from_instructions(text);deps.linearize().into_iter().collect()}
Part 2
In part 2, I'm pretty sure we just re-invented the event loop? This mega-function could probably be boiled down to helper functions, but I'm OK with this for now.
// Part 2: How long will it take to complete all the steps?implDependencyGraph{/// Calculate how long it would take if each step has a duration and/// you have some elves helping youpubfnassisted_assembly_duration(&self,workers:usize,base_delay:usize)->usize{letmutpending:HashSet<char>=self.instructions.keys().cloned().collect();letmutactive:HashMap<char,usize>=HashMap::new();letmutclock:usize=0;whilepending.len()+active.len()>0{// check for completed stepsletcompleted:Vec<char>=active.iter().filter(|(_c,finish)|clock>=**finish).map(|(c,_finish)|c).cloned().collect();forletterincompleted{active.remove(&letter);}// Get any tasks available to be worked onletmutsatisfied:Vec<char>=self.instructions.iter().filter(|(c,deps)|{pending.contains(c)&&deps.iter().all(|dep|!pending.contains(dep)&&!active.contains_key(dep))}).map(|(c,deps)|c.clone()).collect();satisfied.sort();// Give any free workers a tasklettasks_to_assign=cmp::min(workers-active.len(),satisfied.len());forletterinsatisfied.iter().take(tasks_to_assign){// This job will get done duration + base_delay seconds from nowactive.insert(*letter,DependencyGraph::duration_for(*letter)+base_delay+clock);pending.remove(letter);}clock+=1;}// Un-tick the clock, since the clock ticks at the end.clock-1}/// Calculates how long a letter will take to process/// /// The duration of each letter is increased by one as the letters/// increase, starting at A = 1/// Assumes (correctly) that all letters are capitalfnduration_for(letter:char)->usize{(letterasusize)-('A'asusize)+1}}/// Find out how long to run a set of tasks with helperspubfnassisted_duration(text:&str,workers:usize,base_delay:usize)->usize{letdeps=DependencyGraph::from_instructions(text);deps.assisted_assembly_duration(workers,base_delay)}
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!
Javascript solution
I decided to create a tree to represent each Step, adding dependencies and dependents for each steap, and a sorting algorithm to sort steps about to processed (i.e. when it has no dependents).
Then, I use a BFS traversal, employing a queue to visit the steps. In the 1st part, if a step has been processed but still has dependents, I re-add it to the end queue. For the 2nd part, I don't need this because then it won't be picked up by another worker, it remains with the same worker for the rest of the duration of that step.
reader.js
07-common.js
07a.js
07b.js
Today I could figure out only the first part :( Wasted quite a bit of time because example input and real input had one big difference between data structures (and I assumed wrongly ;).
Part 1
readFile.php
Yeah, I wish the sample was more representative of the actual data. I struggled with getting this part 1 done, and was not ready for part 2, to many differences and I had already spent more time on day 7 than any other day
Fighting my way up the leaderboard.
My Python Solution
Part 1
Part 2
KotlinTinkerPop/Gremlin SolutionPart 1
I feel really bad, but I kept banging my head on graph walking, so I gave up and pulled in a graph database to handle that for me.
My first solution was just stepping through
g.V().not(inE()).order().by(property("name").value()).limit(1)
and then dropping each one.However, here's what a real property graph database solution would look like:
Notes: If you see
US
in the code, that's a quick kotlinization of the__
groovy dsl, which doesn't play nicely with IntelliJ.Part 2
Ok, now that I had a method for stepping through the graph, it was much easier to add resources and have time get updated in the DB as I traversed and deleted nodes.
At least, it would have been if I remembered how to gremlin. I spent more time than I'm willing to admit relearning my
group()
andorder()
semantics. Then I spent some time trying to make sure that my traversal prioritized items that were already started, as the sample gave different answers if your workers changed steps willy-nilly.Post-submission optimization: you can just step down the minimum time of the nodes being worked on instead of 1.
This is my C# solution. I'm using Advent of Code this year to practice C# so I'm sure there's lots of opportunity for improvement, but it works! I only work on these in the half hour between getting to work and my workday starting (don't ask) and sometimes at lunch, so I might fall pretty far behind after this weekend...
I quite liked this puzzle! It took me a few false starts-- I couldn't decide whether to track a step's dependencies or dependents --but ultimately I quite like the way my code turned out. Calling
sort
on every step seemed...wasteful...to me, but it was fast enough, so it all worked out in the end.Here's my Julia code (boring parsing, etc. omitted but available here)
Part 2 was also enjoyable-- I briefly considered actually spawning 5 worker threads and keeping a global clock, but then I realized I could just track how much work was being done in between tracking which nodes are now available.
PHP
ohhhhh my god this one took me a really long time for a variety of goofy reasons, mostly not understanding how things were supposed to work correctly
Part 1:
Part 2:
The last part was difficult for me! Took me some time to figure out the small details to take care of.
For now here is my Python code. I am sure it can be simplified by a lot and made much more easy to read. Hopefully I will find time to do it later.
I am building a tree to solve this problem.
I was convinced at first I could sort the steps like this:
It worked on the test data, but not on the full problem. I was really frustrated for a while, and had to google it, coming up with Kahn's algorithm
I went through an imperative, mutation-heavy version first but my Kotlin for part 1 ended up like this:
Once I got there, I found part 2 to be a fairly straightforward extension to parallelise the work and keep track of remaining time. One trick was to make the
Work
ordering put in-progress work first, so on each iterations the workers pulled active steps from the stack first.Got my Rust solution done! After banging my head on it all yesterday, I'm glad I took a break and came back fresh. It seemed to go pretty smoothly the second try at it.
Part 1
Part 2
In part 2, I'm pretty sure we just re-invented the event loop? This mega-function could probably be boiled down to helper functions, but I'm OK with this for now.
Solved super late but:
shared:
p1
p2
Used d3.js to visualize part 1
Awesome again!