From 90a12961f0daa11307e61d95ab753835e7e76574 Mon Sep 17 00:00:00 2001 From: vlabayen Date: Fri, 8 Dec 2023 10:24:41 +0100 Subject: [PATCH 1/4] part 1 --- vLabayen/2023/d8/d8.py | 58 ++++++++++++++++++++++++++++++++++++ vLabayen/2023/d8/example.txt | 9 ++++++ 2 files changed, 67 insertions(+) create mode 100644 vLabayen/2023/d8/d8.py create mode 100644 vLabayen/2023/d8/example.txt diff --git a/vLabayen/2023/d8/d8.py b/vLabayen/2023/d8/d8.py new file mode 100644 index 0000000..a017e25 --- /dev/null +++ b/vLabayen/2023/d8/d8.py @@ -0,0 +1,58 @@ +import logging +from typing import List, Tuple +from attrs import define +from enum import Enum +import re + +class Step(Enum): + LEFT = 'L' + RIGHT = 'R' + +def steps_gen(steps: List[Step]): + while True: + for step in steps: yield step + +@define +class Node: + name : str + left : str + right: str + +parse_node_rgx = re.compile(r'([A-Z]+) = \(([A-Z]+), ([A-Z]+)\)') +def read_file(file: str) -> Tuple[List[Step], List[Node]]: + with open(file, 'r') as f: + lines = (l.strip() for l in f) + + steps = [Step(step) for step in next(lines)] + next(lines) + nodes = [Node(*parse_node_rgx.match(node).groups()) for node in lines] # type: ignore + + return steps, nodes + +def p1(args): + steps, nodes = read_file(args.file) + steps_network = {node.name: node for node in nodes} + + current_node = steps_network['AAA'] + for i, step in enumerate(steps_gen(steps)): + next_node_name = current_node.right if step == Step.RIGHT else current_node.left + current_node = steps_network[next_node_name] + + if current_node.name == 'ZZZ': break + + print(i + 1) # type: ignore + +def p2(args): + _ = read_file(args.file) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('file', type=str, default='input.txt') + parser.add_argument('-v', '--verbose', type=str, choices={'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'}, default='WARNING') + args = parser.parse_args() + + logging.basicConfig(level=args.verbose) + + p1(args) + p2(args) diff --git a/vLabayen/2023/d8/example.txt b/vLabayen/2023/d8/example.txt new file mode 100644 index 0000000..9029a1b --- /dev/null +++ b/vLabayen/2023/d8/example.txt @@ -0,0 +1,9 @@ +RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ) From 54660d856ec5aa9d3ae267ce7f6daf66484ecf61 Mon Sep 17 00:00:00 2001 From: vlabayen Date: Fri, 8 Dec 2023 10:26:25 +0100 Subject: [PATCH 2/4] fix typo --- vLabayen/2023/d8/d8.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vLabayen/2023/d8/d8.py b/vLabayen/2023/d8/d8.py index a017e25..ad335e9 100644 --- a/vLabayen/2023/d8/d8.py +++ b/vLabayen/2023/d8/d8.py @@ -31,12 +31,12 @@ def read_file(file: str) -> Tuple[List[Step], List[Node]]: def p1(args): steps, nodes = read_file(args.file) - steps_network = {node.name: node for node in nodes} + nodes_network = {node.name: node for node in nodes} - current_node = steps_network['AAA'] + current_node = nodes_network['AAA'] for i, step in enumerate(steps_gen(steps)): next_node_name = current_node.right if step == Step.RIGHT else current_node.left - current_node = steps_network[next_node_name] + current_node = nodes_network[next_node_name] if current_node.name == 'ZZZ': break From 423e53330df9c7c939aae6c84539fdeffa92b952 Mon Sep 17 00:00:00 2001 From: vlabayen Date: Fri, 8 Dec 2023 10:34:33 +0100 Subject: [PATCH 3/4] refactor --- vLabayen/2023/d8/d8.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vLabayen/2023/d8/d8.py b/vLabayen/2023/d8/d8.py index ad335e9..f5f9d10 100644 --- a/vLabayen/2023/d8/d8.py +++ b/vLabayen/2023/d8/d8.py @@ -1,5 +1,5 @@ import logging -from typing import List, Tuple +from typing import List, Tuple, Iterable from attrs import define from enum import Enum import re @@ -8,9 +8,10 @@ class Step(Enum): LEFT = 'L' RIGHT = 'R' -def steps_gen(steps: List[Step]): +def steps_gen(steps: List[Step]) -> Iterable[Tuple[int, Step]]: while True: - for step in steps: yield step + for i, step in enumerate(steps): + yield i, step @define class Node: @@ -34,7 +35,7 @@ def p1(args): nodes_network = {node.name: node for node in nodes} current_node = nodes_network['AAA'] - for i, step in enumerate(steps_gen(steps)): + for i, (_, step) in enumerate(steps_gen(steps)): next_node_name = current_node.right if step == Step.RIGHT else current_node.left current_node = nodes_network[next_node_name] From c05ab3c5f1692f6e2ee10046db7de0a3cca0560e Mon Sep 17 00:00:00 2001 From: vlabayen Date: Fri, 8 Dec 2023 11:18:21 +0100 Subject: [PATCH 4/4] part 2 --- vLabayen/2023/d8/d8.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/vLabayen/2023/d8/d8.py b/vLabayen/2023/d8/d8.py index f5f9d10..c8210f7 100644 --- a/vLabayen/2023/d8/d8.py +++ b/vLabayen/2023/d8/d8.py @@ -1,8 +1,14 @@ import logging -from typing import List, Tuple, Iterable +from typing import List, Tuple, Iterable, Dict from attrs import define from enum import Enum import re +from itertools import takewhile +from functools import reduce +from math import gcd + +def lcm(numbers: Iterable[int]) -> int: + return reduce(lambda a, b: a * b // gcd(a, b), numbers) class Step(Enum): LEFT = 'L' @@ -43,8 +49,34 @@ def p1(args): print(i + 1) # type: ignore + +def detect_loop_frequency(node: Node, steps: List[Step], nodes_network: Dict[str, Node]) -> int: + visited_nodes: Dict[Tuple[str, int], int] = {} + current_node = node + + for i, (step_idx, step) in enumerate(steps_gen(steps)): + key = (current_node.name, step_idx) + + past_visit = visited_nodes.get(key, None) + if past_visit is not None: + return i - past_visit + visited_nodes[key] = i + + next_node_name = current_node.right if step == Step.RIGHT else current_node.left + current_node = nodes_network[next_node_name] + + raise + def p2(args): - _ = read_file(args.file) + steps, nodes = read_file(args.file) + nodes_network = {node.name: node for node in nodes} + + ending_A_nodes = [node for node in nodes if node.name.endswith('A')] + nodes_cycle_freq = {node.name: detect_loop_frequency(node, steps, nodes_network) for node in ending_A_nodes} + + # As far a I can think, there is no hard requirement for the ending-Z nodes to be located exactly at the cycle freq + # nevertheless, that's seems that be what happens in the example & my input. + print(lcm(freq for freq in nodes_cycle_freq.values())) if __name__ == '__main__': import argparse