diff --git a/challenges/2024/09-diskFragmenter/README.md b/challenges/2024/09-diskFragmenter/README.md new file mode 100644 index 0000000..c9be906 --- /dev/null +++ b/challenges/2024/09-diskFragmenter/README.md @@ -0,0 +1,5 @@ +# [Day 9: Disk Fragmenter](https://adventofcode.com/2024/day/9) + +Part 2 is: +* less than 6277851188082 + 6272188244509 \ No newline at end of file diff --git a/challenges/2024/09-diskFragmenter/main.py b/challenges/2024/09-diskFragmenter/main.py new file mode 100644 index 0000000..f807bd9 --- /dev/null +++ b/challenges/2024/09-diskFragmenter/main.py @@ -0,0 +1,129 @@ +import sys +from collections import namedtuple, defaultdict + + +Segment = namedtuple("Segment", ["id", "len"]) + + +def parse(instr: str) -> list[Segment]: + res = [] + next_id = 0 + + for i, v in enumerate(instr): + v = int(v) + if i % 2 == 0: + # this is a file + res.append(Segment(next_id, v)) + next_id += 1 + else: + # this is a gap + res.append(Segment(None, v)) + + if res[-1].id is None: + # random gap at the end? no thanks + res = res[:-1] + + return res + + +def calc_checksum(files: list[Segment]) -> int: + acc = 0 + pos = 0 + for file in files: + if file.id is not None: + # sum of sequence of n consecutive integers is (n / 2)(first + last) + acc += int((file.len / 2) * ((pos * 2) + file.len - 1) * file.id) + pos += file.len + return acc + + +def one(instr: str): + files = parse(instr) + + i = 0 + while i < len(files) - 1: + f = files[i] + last_file = files[-1] + + if last_file.id is None: + files = files[:-1] + continue + + if f.id is None: + # _debug(i, files[i]) + last_file = files[-1] + + assert last_file.id is not None + if last_file.len > f.len: + files[-1] = Segment(last_file.id, last_file.len - f.len) + files[i] = Segment(last_file.id, f.len) + elif last_file.len == f.len: + # we're gonna move all of this so just delete it + files = files[:-1] + files[i] = last_file + else: + # TODO: when we haven't got enough in this last file so we need to split f into two + files = files[:-1] + files[i] = last_file + assert f.len - last_file.len != 0 + files.insert(i+1, Segment(None, f.len - last_file.len)) + + i += 1 + + return calc_checksum(files) + + +def two(instr: str): + files = parse(instr) + + gaps = defaultdict(list) + + for i, file in enumerate(files): + if file.id is None and file.len != 0: + gaps[file.len].append(i) + + moved = set() + + i = len(files) - 1 + while i > 0: + f = files[i] + + if f.id is not None and f.id not in moved: + gap_loc = None + for j, v in enumerate(files): + if j >= i: + break + + if v.id is None and v.len >= f.len: + gap_loc = j + break + + if gap_loc is not None: + v = files[gap_loc] + if v.len == f.len: + files[i], files[gap_loc] = files[gap_loc], files[i] + else: + files[i] = Segment(None, f.len) + files[gap_loc] = f + files.insert(gap_loc + 1, Segment(None, v.len - f.len)) + moved.add(f.id) + + i -= 1 + + return calc_checksum(files) + + +def _debug(*args, **kwargs): + kwargs["file"] = sys.stderr + print(*args, **kwargs) + + +if __name__ == "__main__": + if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]: + print("Missing day argument", file=sys.stderr) + sys.exit(1) + inp = sys.stdin.read().strip() + if sys.argv[1] == "1": + print(one(inp)) + else: + print(two(inp)) \ No newline at end of file diff --git a/challenges/2024/09-diskFragmenter/tests.json b/challenges/2024/09-diskFragmenter/tests.json new file mode 100644 index 0000000..737ab31 --- /dev/null +++ b/challenges/2024/09-diskFragmenter/tests.json @@ -0,0 +1,14 @@ +{ + "1": [ + { + "is": "1928", + "input": "2333133121414131402\n" + } + ], + "2": [ + { + "is": "2858", + "input": "2333133121414131402\n" + } + ] +} \ No newline at end of file diff --git a/challenges/2024/README.md b/challenges/2024/README.md index 8ce0148..78f7970 100644 --- a/challenges/2024/README.md +++ b/challenges/2024/README.md @@ -21,4 +21,5 @@ A day denoted with an asterisk means it has a visualisation. | 05 - Print Queue | ★ ★ | Python | Before you dismiss and idea as being "too simple", make sure you check that it doesn't work. | | 06 - Guard Gallivant | ★ ★ | Python | oh dear runtime (also I knew what I wanted to do for so long it just took me 3 hours to implement it properly) | | 07 - Bridge Repair | ★ ★ | Python | Maths? Backwards?? | -| 08* - Resonant Collinearity | ★ ★ | Python | `Fraction` saving us all from the curse of a computer's inability to do floating point arithmetic | \ No newline at end of file +| 08* - Resonant Collinearity | ★ ★ | Python | `Fraction` saving us all from the curse of a computer's inability to do floating point arithmetic | +| 09 - Disk Fragmenter | ★ ★ | Python | Many cursed strategies were attempted before I landed on the final implementation | \ No newline at end of file diff --git a/challenges/2024/benchmark-graph.png b/challenges/2024/benchmark-graph.png index 0ffd571..4b35c8f 100644 Binary files a/challenges/2024/benchmark-graph.png and b/challenges/2024/benchmark-graph.png differ diff --git a/challenges/2024/benchmarks.jsonl b/challenges/2024/benchmarks.jsonl index 69ae1bc..ee808c7 100644 --- a/challenges/2024/benchmarks.jsonl +++ b/challenges/2024/benchmarks.jsonl @@ -14,3 +14,5 @@ {"day": 7, "part": 2, "runner": "py", "min": 0.02680826187133789, "max": 0.04485297203063965, "avg": 0.029925800323486327, "n": 500} {"day": 8, "part": 1, "runner": "py", "min": 0.025803089141845703, "max": 0.036757469177246094, "avg": 0.02743640899658203, "n": 200} {"day": 8, "part": 2, "runner": "py", "min": 0.027710437774658203, "max": 0.035851240158081055, "avg": 0.029560294151306152, "n": 200} +{"day": 9, "part": 1, "runner": "py", "min": 0.5576984882354736, "max": 0.5812969207763672, "avg": 0.5654887676239013, "n": 5} +{"day": 9, "part": 2, "runner": "py", "min": 3.876859188079834, "max": 4.176096677780151, "avg": 4.0687650680542, "n": 5}