diff --git a/py/2018/07/solve.py b/py/2018/07/solve.py deleted file mode 100644 index 9f685bc..0000000 --- a/py/2018/07/solve.py +++ /dev/null @@ -1,115 +0,0 @@ -import re -import sys - -# PART 1 - -STEP_RE = r"Step (\S+) must be finished before step (\S+) can begin.\n" - -def load_steps(filename): - steps = {} - with open(filename, "r") as f: - for line in f: - match = re.fullmatch(STEP_RE, line) - step, before = match.groups() - steps[step] = steps.get(step, set()) | {before} - - return steps - -def reverse_steps(steps): - reverse = {} - for step, befores in steps.items(): - # Make sure that step exists in reverse - reverse[step] = reverse.get(step, set()) - - for before in befores: - reverse[before] = reverse.get(before, set()) | {step} - - return reverse - -def duration_of(step): - return ord(step) - ord("A") + 61 - -class Tree: - def __init__(self, steps, workers=1): - self.workers = {i: None for i in range(workers)} - self.steps = reverse_steps(steps) # Warning: Steps are reversed in Trees. - self.result = [] - self.duration = 0 - - def working(self): - return {worker: work for worker, work in self.workers.items() if work is not None} - - def find_free(self): - return {step for step, afters in self.steps.items() if len(afters) == 0} - - def find_working(self): - return {step for (step, _) in self.working().values()} - - def find_available(self): - return self.find_free() - self.find_working() - - def remove_step(self, step): - try: - del self.steps[step] - except KeyError: - pass - - for s in self.steps.values(): - try: - s.remove(step) - except KeyError: - pass - - def update_workers(self): - min_duration = min(duration for (_, duration) in self.working().values()) - self.duration += min_duration - - finished_steps = set() - - # Subtract min_duration from all workers - for w, s in self.workers.items(): - if s is not None: - step, duration = s - duration -= min_duration - - if duration <= 0: - finished_steps.add(step) - self.remove_step(step) - self.workers[w] = None - else: - self.workers[w] = (step, duration) - - self.result += list(finished_steps) - - def distribute_jobs(self): - available = list(reversed(sorted(self.find_available()))) - - for w, s in self.workers.items(): - if not available: - break - - if s is None: - step = available.pop() - duration = duration_of(step) - self.workers[w] = (step, duration) - - def run(self): - while self.steps: - self.distribute_jobs() - self.update_workers() - -def main(filename): - print(f"Solutions for {filename}") - steps = load_steps(filename) - tree = Tree(steps, workers=1) - tree.run() - sequence = "".join(tree.result) - print(f"Part 1: {sequence}") - tree = Tree(steps, workers=5) - tree.run() - duration = tree.duration - print(f"Part 2: {duration}") - -if __name__ == "__main__": - for filename in sys.argv[1:]: - main(filename) diff --git a/py/2018/07/test_input.txt b/py/2018/07/test_input.txt deleted file mode 100644 index 9ab25bf..0000000 --- a/py/2018/07/test_input.txt +++ /dev/null @@ -1,7 +0,0 @@ -Step C must be finished before step A can begin. -Step C must be finished before step F can begin. -Step A must be finished before step B can begin. -Step A must be finished before step D can begin. -Step B must be finished before step E can begin. -Step D must be finished before step E can begin. -Step F must be finished before step E can begin. diff --git a/py/aoc/__init__.py b/py/aoc/__init__.py index 96d9d54..44c395f 100644 --- a/py/aoc/__init__.py +++ b/py/aoc/__init__.py @@ -2,7 +2,7 @@ import sys import argparse from pathlib import Path -from .y2018 import d01, d02, d03, d04, d05, d06 +from .y2018 import d01, d02, d03, d04, d05, d06, d07 from .y2020 import d10 from .y2021 import d14 from .y2022 import d01, d02, d03, d04, d05, d06 @@ -14,6 +14,7 @@ DAYS = { "2018_04": y2018.d04.solve, "2018_05": y2018.d05.solve, "2018_06": y2018.d06.solve, + "2018_07": y2018.d07.solve, "2020_10": y2020.d10.solve, "2021_14": y2021.d14.solve, "2022_01": y2022.d01.solve, diff --git a/py/aoc/y2018/d07.py b/py/aoc/y2018/d07.py new file mode 100644 index 0000000..78b0544 --- /dev/null +++ b/py/aoc/y2018/d07.py @@ -0,0 +1,115 @@ +import re + +# PART 1 + +STEP_RE = r"Step (\S+) must be finished before step (\S+) can begin." + + +def load_steps(inputstr): + steps = {} + for line in inputstr.splitlines(): + match = re.fullmatch(STEP_RE, line) + step, before = match.groups() + steps[step] = steps.get(step, set()) | {before} + + return steps + + +def reverse_steps(steps): + reverse = {} + for step, befores in steps.items(): + # Make sure that step exists in reverse + reverse[step] = reverse.get(step, set()) + + for before in befores: + reverse[before] = reverse.get(before, set()) | {step} + + return reverse + + +def duration_of(step): + return ord(step) - ord("A") + 61 + + +class Tree: + def __init__(self, steps, workers=1): + self.workers = {i: None for i in range(workers)} + self.steps = reverse_steps(steps) # Warning: Steps are reversed in Trees. + self.result = [] + self.duration = 0 + + def working(self): + return { + worker: work for worker, work in self.workers.items() if work is not None + } + + def find_free(self): + return {step for step, afters in self.steps.items() if len(afters) == 0} + + def find_working(self): + return {step for (step, _) in self.working().values()} + + def find_available(self): + return self.find_free() - self.find_working() + + def remove_step(self, step): + try: + del self.steps[step] + except KeyError: + pass + + for s in self.steps.values(): + try: + s.remove(step) + except KeyError: + pass + + def update_workers(self): + min_duration = min(duration for (_, duration) in self.working().values()) + self.duration += min_duration + + finished_steps = set() + + # Subtract min_duration from all workers + for w, s in self.workers.items(): + if s is not None: + step, duration = s + duration -= min_duration + + if duration <= 0: + finished_steps.add(step) + self.remove_step(step) + self.workers[w] = None + else: + self.workers[w] = (step, duration) + + self.result += list(finished_steps) + + def distribute_jobs(self): + available = list(reversed(sorted(self.find_available()))) + + for w, s in self.workers.items(): + if not available: + break + + if s is None: + step = available.pop() + duration = duration_of(step) + self.workers[w] = (step, duration) + + def run(self): + while self.steps: + self.distribute_jobs() + self.update_workers() + + +def solve(inputstr): + steps = load_steps(inputstr) + tree = Tree(steps, workers=1) + tree.run() + sequence = "".join(tree.result) + print(f"Part 1: {sequence}") + tree = Tree(steps, workers=5) + tree.run() + duration = tree.duration + print(f"Part 2: {duration}")