85 lines
3.8 KiB
Python
85 lines
3.8 KiB
Python
|
|
import itertools
|
||
|
|
|
||
|
|
from solutions import BaseSolution
|
||
|
|
|
||
|
|
|
||
|
|
class Solution(BaseSolution):
|
||
|
|
input_file = '07.in'
|
||
|
|
def __str__(self):
|
||
|
|
return 'Day 07: The Sum of Its Parts'
|
||
|
|
|
||
|
|
def solve(self, puzzle_input):
|
||
|
|
steps = [tuple(filter(lambda x: len(x) == 1, l.split())) for l in puzzle_input.splitlines()]
|
||
|
|
relations = self._get_relations(steps)
|
||
|
|
completed_steps = self._get_completed_steps(relations)
|
||
|
|
return completed_steps
|
||
|
|
|
||
|
|
def solve_again(self, puzzle_input):
|
||
|
|
return self.solve_with_workers(puzzle_input)
|
||
|
|
|
||
|
|
def solve_with_workers(self, puzzle_input, workers=5, completion_reducer=0):
|
||
|
|
steps = [tuple(filter(lambda x: len(x) == 1, l.split())) for l in puzzle_input.splitlines()]
|
||
|
|
relations = self._get_relations(steps)
|
||
|
|
return self.get_seconds(relations, workers, completion_reducer)
|
||
|
|
|
||
|
|
def _get_completed_steps(self, relations):
|
||
|
|
stop_at = len(relations.keys())
|
||
|
|
completed_steps = ''
|
||
|
|
while len(completed_steps) < stop_at:
|
||
|
|
available = sorted([child for child, parents in relations.items()
|
||
|
|
if (all(prnt in completed_steps for prnt in parents) or len(parents) == 0) and child not in completed_steps])
|
||
|
|
completed = available[0]
|
||
|
|
completed_steps += completed
|
||
|
|
return completed_steps
|
||
|
|
|
||
|
|
def _get_relations(self, steps):
|
||
|
|
deps = dict([(p, set()) for p in itertools.chain(dict(steps).keys(), dict(steps).values())])
|
||
|
|
for parent, child in steps:
|
||
|
|
deps[child].add(parent)
|
||
|
|
return deps
|
||
|
|
|
||
|
|
def get_seconds(self, relations, workers_count=5, completion_reduce=0):
|
||
|
|
workers = [dict() for _ in range(workers_count)]
|
||
|
|
stop_at = len(relations)
|
||
|
|
seconds = 0
|
||
|
|
completed = 0
|
||
|
|
completed_steps = ''
|
||
|
|
in_progress = set()
|
||
|
|
diff = ord('A')
|
||
|
|
while completed < stop_at:
|
||
|
|
available_steps = sorted([child for child, parents in relations.items()
|
||
|
|
if (all(prnt in completed_steps for prnt in parents) or len(parents) == 0) and
|
||
|
|
child not in completed_steps])
|
||
|
|
available_steps = [s for s in available_steps if s not in in_progress and s not in completed_steps]
|
||
|
|
available_workers = [i for i, w in enumerate(workers) if not w]
|
||
|
|
while available_steps and available_workers:
|
||
|
|
avs = available_steps.pop(0)
|
||
|
|
avw = available_workers.pop(0)
|
||
|
|
in_progress.add(avs)
|
||
|
|
workers[avw][avs] = 0
|
||
|
|
busy_workers = [i for i, w in enumerate(workers) if w]
|
||
|
|
for ip, bw in itertools.product(in_progress, busy_workers):
|
||
|
|
if ip in workers[bw]:
|
||
|
|
# Apply completion_reduce (60) here since A takes 61 seconds to complete,
|
||
|
|
# B 62 times to complete, etc in THE REAL WORLD, but in the example it was "simplified".
|
||
|
|
#
|
||
|
|
# > To simplify things for the example, however, suppose you only have help from one Elf (a total
|
||
|
|
# > of two workers) and that each step takes **60 fewer seconds** (so that step A takes 1 second and
|
||
|
|
# > step Z takes 26 seconds).
|
||
|
|
#
|
||
|
|
# Die in a fire, would you kindly.
|
||
|
|
if workers[bw][ip] == 60 - completion_reduce + ord(ip) - diff:
|
||
|
|
completed_steps += ip
|
||
|
|
in_progress.remove(ip)
|
||
|
|
del workers[bw][ip]
|
||
|
|
completed += 1
|
||
|
|
else:
|
||
|
|
workers[bw][ip] += 1
|
||
|
|
seconds += 1
|
||
|
|
return seconds
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
solution = Solution()
|
||
|
|
solution.show_results()
|