From 6547d1baf6e0735b4853c48d3dcc8078a52f1657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Kope=C4=87?= Date: Sat, 9 Dec 2023 07:22:07 +0100 Subject: [PATCH] Solutions day 6-9 --- src/2023/06/2023_06.py | 43 ++++++++++++++ src/2023/06/sample_input.txt | 2 + src/2023/07/2023_07.py | 112 +++++++++++++++++++++++++++++++++++ src/2023/07/sample_input.txt | 3 + src/2023/08/2023_08.py | 95 +++++++++++++++++++++++++++++ src/2023/08/sample_input.txt | 11 ++++ src/2023/09/2023_09.py | 57 ++++++++++++++++++ src/2023/09/sample_input.txt | 2 + 8 files changed, 325 insertions(+) create mode 100644 src/2023/06/2023_06.py create mode 100644 src/2023/06/sample_input.txt create mode 100644 src/2023/07/2023_07.py create mode 100644 src/2023/07/sample_input.txt create mode 100644 src/2023/08/2023_08.py create mode 100644 src/2023/08/sample_input.txt create mode 100644 src/2023/09/2023_09.py create mode 100644 src/2023/09/sample_input.txt diff --git a/src/2023/06/2023_06.py b/src/2023/06/2023_06.py new file mode 100644 index 0000000..6aaed00 --- /dev/null +++ b/src/2023/06/2023_06.py @@ -0,0 +1,43 @@ +import operator +import re +from functools import reduce + +from src.utils.data import load_data +from src.utils.submission import submit_or_print + + +def main(debug: bool) -> None: + input_data = load_data(debug) + + # part 1 + lines = input_data.splitlines() + times = map(int, re.findall(r"\d+", lines[0])) + distances = map(int, re.findall(r"\d+", lines[1])) + numbers = [] + for time, record_distance in zip(times, distances): + ways = 0 + for hold_time in range(1, time + 1): + distance = hold_time * (time - hold_time) + if distance > record_distance: + ways += 1 + numbers.append(ways) + result_part1 = reduce(operator.mul, numbers) + + # part 2 + lines = input_data.splitlines() + time = int("".join(re.findall(r"\d", lines[0]))) + record_distance = int("".join(re.findall(r"\d", lines[1]))) + ways = 0 + for hold_time in range(1, time + 1): + distance = hold_time * (time - hold_time) + if distance > record_distance: + ways += 1 + result_part2 = ways + + submit_or_print(result_part1, result_part2, debug) + + +if __name__ == "__main__": + debug_mode = True + # debug_mode = False + main(debug_mode) diff --git a/src/2023/06/sample_input.txt b/src/2023/06/sample_input.txt new file mode 100644 index 0000000..040bd29 --- /dev/null +++ b/src/2023/06/sample_input.txt @@ -0,0 +1,2 @@ +Time: 6 56 20 +Distance: 5 33 100 diff --git a/src/2023/07/2023_07.py b/src/2023/07/2023_07.py new file mode 100644 index 0000000..7018c26 --- /dev/null +++ b/src/2023/07/2023_07.py @@ -0,0 +1,112 @@ +import dataclasses +import enum +from collections import Counter +from functools import cmp_to_key, partial +from typing import Callable, List + +from src.utils.data import load_data +from src.utils.submission import submit_or_print + +CARD_ORDER = ["A", "K", "Q", "T", *[str(i) for i in range(9, 1, -1)], "J"] + + +def card_rank(card: str) -> int: + return CARD_ORDER.index(card) + + +class Type(enum.IntEnum): + FIVE = 1 + FOUR = 2 + FULL = 3 + THREE = 4 + TWO = 5 + ONE = 6 + HIGH = 7 + + +@dataclasses.dataclass +class Hand: + cards: List[str] + bid: int + + def hand_type_part1(self) -> Type: + return self._hand_type(self.cards) + + def hand_type_part2(self) -> Type: + non_joker_counts = Counter(filter(lambda c: c != "J", self.cards)) + if not non_joker_counts: + return Type.FIVE + ordered = non_joker_counts.most_common() + most_common = ordered[0] + cards = [(most_common[0] if c == "J" else c) for c in self.cards] + return self._hand_type(cards) + + @staticmethod + def _hand_type(cards: List[str]) -> Type: + counts = Counter(cards) + most_common = counts.most_common()[0] + if most_common[1] == 5: + return Type.FIVE + if most_common[1] == 4: + return Type.FOUR + second_most_common = counts.most_common()[1] + if most_common[1] == 3: + if second_most_common[1] == 2: + return Type.FULL + return Type.THREE + if most_common[1] == 2: + if second_most_common[1] == 2: + return Type.TWO + return Type.ONE + return Type.HIGH + + @staticmethod + def compare(first: "Hand", second: "Hand", hand_type_function) -> bool: + first_type = hand_type_function(first) + second_type = hand_type_function(second) + if first_type == second_type: + for c1, c2 in zip(first.cards, second.cards): + c1_rank = card_rank(c1) + c2_rank = card_rank(c2) + if c1_rank == c2_rank: + continue + return c1_rank > c2_rank + return first_type > second_type + + +def main(debug: bool) -> None: + input_data = load_data(debug) + hands = parse_input(input_data) + + result_part1 = solve( + hands, partial(Hand.compare, hand_type_function=Hand.hand_type_part1) + ) + result_part2 = solve( + hands, partial(Hand.compare, hand_type_function=Hand.hand_type_part2) + ) + + submit_or_print(result_part1, result_part2, debug) + + +def parse_input(input_data: str) -> List[Hand]: + hands = [] + for line in input_data.splitlines(): + cards = [c for c in line[:5]] + bid = int(line.split(" ")[1]) + hands.append(Hand(cards, bid)) + return hands + + +def solve(hands: List[Hand], compare_function: Callable[[Hand, Hand], bool]) -> int: + total = 0 + for rank, hand in enumerate( + sorted(hands, key=cmp_to_key(compare_function)), start=1 + ): + total += rank * hand.bid + return total + + +if __name__ == "__main__": + debug_mode = True + # debug_mode = False + main(debug_mode) diff --git a/src/2023/07/sample_input.txt b/src/2023/07/sample_input.txt new file mode 100644 index 0000000..f6c0b36 --- /dev/null +++ b/src/2023/07/sample_input.txt @@ -0,0 +1,3 @@ +255JJ 123 +JAQDT 333 +22222 258 diff --git a/src/2023/08/2023_08.py b/src/2023/08/2023_08.py new file mode 100644 index 0000000..e8a2ad6 --- /dev/null +++ b/src/2023/08/2023_08.py @@ -0,0 +1,95 @@ +import dataclasses +import math +import re +from typing import Dict, List, Optional, Tuple + +from src.utils.data import load_data +from src.utils.submission import submit_or_print + + +@dataclasses.dataclass +class Node: + name: str + left: Optional["Node"] + right: Optional["Node"] + + def next(self, direction: str): + if direction == "L": + return self.left + elif direction == "R": + return self.right + else: + raise AttributeError(f"Unknown direction {direction}") + + def __eq__(self, other): + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + def is_final_part1(self): + return self.name == "ZZZ" + + def is_final_part2(self): + return self.name.endswith("Z") + + +def main(debug: bool) -> None: + input_data = load_data(debug) + + instructions, nodes = parse_input(input_data) + print(f"{len(instructions)} instructions") + print(f"{len(nodes)} nodes") + + # part 1 + node = nodes["AAA"] + instruction_index = 0 + step = 0 + while node.name != "ZZZ": + node = node.next(instructions[instruction_index]) + instruction_index = (instruction_index + 1) % len(instructions) + step += 1 + result_part1 = step + + # part 2 + starting_nodes = {node for node in nodes.values() if node.name[-1] == "A"} + steps_with_final_node = set() + for node in starting_nodes: + current = node + visited = set() + instruction_index = 0 + step = 0 + while (instruction_index, current.name) not in visited: + visited.add((instruction_index, current.name)) + if current.is_final_part2(): + steps_with_final_node.add(step) + current = current.next(instructions[instruction_index]) + instruction_index = (instruction_index + 1) % len(instructions) + step += 1 + result_part2 = math.lcm(*steps_with_final_node) + + submit_or_print(result_part1, result_part2, debug) + + +def parse_input(input_data: str) -> Tuple[List[str], Dict[str, Node]]: + lines = input_data.splitlines() + instructions = list(lines[0]) + nodes = {} + nodes_str = {} + for line in lines[2:]: + m = re.match(r"([A-Z0-9]+) = \(([A-Z0-9]+), ([A-Z0-9]+)\)", line) + name = m.group(1) + left = m.group(2) + right = m.group(3) + nodes[name] = Node(name, None, None) + nodes_str[name] = (left, right) + for name, node in nodes.items(): + node.left = nodes[nodes_str[node.name][0]] + node.right = nodes[nodes_str[node.name][1]] + return instructions, nodes + + +if __name__ == "__main__": + debug_mode = True + # debug_mode = False + main(debug_mode) diff --git a/src/2023/08/sample_input.txt b/src/2023/08/sample_input.txt new file mode 100644 index 0000000..ed23617 --- /dev/null +++ b/src/2023/08/sample_input.txt @@ -0,0 +1,11 @@ +LRLR + +AAA = (ZZZ, ZZZ) +BBA = (BBB, YYY) +BBB = (YYY, XXZ) +XXZ = (BBB, YYY) +CCA = (CCB, YYY) +CCB = (CCZ, CCZ) +CCZ = (CCB, CCB) +YYY = (YYY, YYY) +ZZZ = (ZZZ, ZZZ) diff --git a/src/2023/09/2023_09.py b/src/2023/09/2023_09.py new file mode 100644 index 0000000..2a1fd40 --- /dev/null +++ b/src/2023/09/2023_09.py @@ -0,0 +1,57 @@ +from typing import Callable, List + +from src.utils.data import load_data +from src.utils.submission import submit_or_print + + +def main(debug: bool) -> None: + input_data = load_data(debug) + + seqs = parse_input(input_data) + + result_part1 = solve(seqs, score_part1) + result_part2 = solve(seqs, score_part2) + + submit_or_print(result_part1, result_part2, debug) + + +def parse_input(input_data: str) -> List[List[int]]: + lines = input_data.splitlines() + seqs = [[int(s) for s in l.split(" ")] for l in lines] + return seqs + + +def solve( + seqs: List[List[int]], scoring_function: Callable[[List[List[int]]], int] +) -> int: + total = 0 + for seq in seqs: + rows = [] + row = seq + while not all([n == 0 for n in row]): + rows.append(row) + row = [(v[1] - v[0]) for v in zip(row, row[1:])] + total += scoring_function(rows) + return total + + +def score_part1(rows: List[List[int]]) -> int: + inc = 0 + for row in reversed(rows): + row.append(row[-1] + inc) + inc = row[-1] + return rows[0][-1] + + +def score_part2(rows: List[List[int]]) -> int: + inc = 0 + for row in reversed(rows): + row.insert(0, (row[0] - inc)) + inc = row[0] + return rows[0][0] + + +if __name__ == "__main__": + debug_mode = True + # debug_mode = False + main(debug_mode) diff --git a/src/2023/09/sample_input.txt b/src/2023/09/sample_input.txt new file mode 100644 index 0000000..4c48a58 --- /dev/null +++ b/src/2023/09/sample_input.txt @@ -0,0 +1,2 @@ +1 4 5 8 23 +0 9 22 33