diff --git a/2025/day11/README.md b/2025/day11/README.md new file mode 100644 index 0000000..9253c1d --- /dev/null +++ b/2025/day11/README.md @@ -0,0 +1,10 @@ +# Day 11: Python + +Straightforward. Uses `networkx` because I thought it would be very helpful but it only really saves +me from writing a topological sort algorithm in the end. + +```console +$ uv run ./solve.py input.txt +Part 1: secret +Part 2: secret +``` diff --git a/2025/day11/pyproject.toml b/2025/day11/pyproject.toml new file mode 100644 index 0000000..66cd3e3 --- /dev/null +++ b/2025/day11/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "solve" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "networkx>=3.6.1", +] diff --git a/2025/day11/sample.txt b/2025/day11/sample.txt new file mode 100644 index 0000000..01e5b43 --- /dev/null +++ b/2025/day11/sample.txt @@ -0,0 +1,10 @@ +aaa: you hhh +you: bbb ccc +bbb: ddd eee +ccc: ddd eee fff +ddd: ggg +eee: out +fff: out +ggg: out +hhh: ccc fff iii +iii: out diff --git a/2025/day11/sample2.txt b/2025/day11/sample2.txt new file mode 100644 index 0000000..2e4c18d --- /dev/null +++ b/2025/day11/sample2.txt @@ -0,0 +1,14 @@ +svr: aaa bbb +aaa: fft +fft: ccc +bbb: tty +tty: ccc +ccc: ddd eee +ddd: hub +hub: fff +eee: dac +dac: fff +fff: ggg hhh +ggg: out +hhh: out +you: out diff --git a/2025/day11/solve.py b/2025/day11/solve.py new file mode 100755 index 0000000..3fa5b8d --- /dev/null +++ b/2025/day11/solve.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +from collections import defaultdict +import fileinput +from typing import Iterable + +import networkx as nx + +def parse_graph() -> nx.Graph: + graph = nx.DiGraph() + + for line in fileinput.input(): + source, rem = line.split(": ") + + for sink in rem.strip().split(" "): + graph.add_edge(source, sink) + + return graph + +def count(gen: Iterable) -> int: + return sum(1 for _ in gen) + +def main() -> None: + graph = parse_graph() + + # Observation: graph is a DAG, so one needs to go in front of the other. We can do this in three + # steps: + # svr → closest(dac, fft) -> furthest(dac,fft) + rank = { + node: rank + for rank, node in enumerate(nx.topological_sort(graph)) + } + + rev_rank = {rank: node for node, rank in rank.items()} + + if rank["dac"] > rank["fft"]: + closest = "fft" + furthest = "dac" + else: + closest = "dac" + furthest = "fft" + + + def ranked_all_paths(source: str, dest: str) -> int: + counts = defaultdict(int) + + counts[dest] = 1 + + for r in range(rank[dest], rank[source], -1): + node = rev_rank[r] + if node not in counts: + continue + + for u, _ in graph.in_edges(node): + counts[u] += counts[node] + + return counts[source] + + assert nx.has_path(graph, closest, furthest) + + print("Part 1", ranked_all_paths("you", "out")) + + first = ranked_all_paths("svr", closest) + second = ranked_all_paths(closest, furthest) + third = ranked_all_paths(furthest, "out") + print("Part 2:", first * second * third) + + +if __name__ == "__main__": + main()