|
| 1 | +from collections.abc import Iterable |
| 2 | +from math import floor |
| 3 | + |
| 4 | +from black.trans import defaultdict |
| 5 | +from more_itertools.recipes import partition |
| 6 | + |
| 7 | +from utils.file import read_input |
| 8 | + |
| 9 | +lines = read_input(__package__) |
| 10 | + |
| 11 | +rules: list[tuple[int, int]] = [] |
| 12 | +updates: list[list[int]] = [] |
| 13 | +for line in lines: |
| 14 | + if line[2] == "|": |
| 15 | + rules.append((int(line[:2]), int(line[3:]))) |
| 16 | + elif "," in line: |
| 17 | + updates.append(list(map(int, line.split(",")))) |
| 18 | + |
| 19 | +rules_after = defaultdict(set) |
| 20 | +rules_before = defaultdict(set) |
| 21 | +for rule in rules: |
| 22 | + rules_after[rule[0]].add(rule[1]) |
| 23 | + rules_before[rule[1]].add(rule[0]) |
| 24 | + |
| 25 | + |
| 26 | +def check_rule(page: int, before: list[int], after: list[int]) -> bool: |
| 27 | + return all(p not in rules_after[page] for p in before) and all(p not in rules_before[page] for p in after) |
| 28 | + |
| 29 | + |
| 30 | +def is_ordered(pages: list[int]) -> bool: |
| 31 | + return all(check_rule(pages[i], pages[:i], pages[i + 1 :]) for i in range(len(pages))) |
| 32 | + |
| 33 | + |
| 34 | +def middle_pages(updates_: Iterable[list[int]]) -> Iterable[int]: |
| 35 | + return (update[floor((len(update) - 1) / 2)] for update in updates_) |
| 36 | + |
| 37 | + |
| 38 | +unordered, ordered = partition(is_ordered, updates) |
| 39 | +print("Part 1", sum(middle_pages(ordered))) |
| 40 | + |
| 41 | + |
| 42 | +def reorder(pages_: list[int]): |
| 43 | + pages = pages_[:] # Copy before mutations |
| 44 | + while not is_ordered(pages): # Pray for no infinite loop |
| 45 | + for i in range(len(pages)): |
| 46 | + page = pages[i] |
| 47 | + before, after = pages[:i], pages[i + 1 :] |
| 48 | + rule_before, rule_after = rules_before[page], rules_after[page] |
| 49 | + |
| 50 | + move_before = next((p for p in before if p in rule_after), None) |
| 51 | + if move_before: |
| 52 | + pages = pages[:i] + pages[i + 1 :] |
| 53 | + idx = pages.index(move_before) |
| 54 | + pages.insert(idx, page) |
| 55 | + break |
| 56 | + move_after = next((p for p in after if p in rule_before), None) |
| 57 | + if move_after: |
| 58 | + pages = pages[:i] + pages[i + 1 :] |
| 59 | + idx = -pages[::-1].index(move_after) |
| 60 | + if idx == 0: |
| 61 | + pages.append(page) |
| 62 | + else: |
| 63 | + pages.insert(idx, page) |
| 64 | + return pages |
| 65 | + |
| 66 | + |
| 67 | +reordered = list(map(reorder, unordered)) |
| 68 | +print("Part 2", sum(middle_pages(reordered))) |
0 commit comments