diff --git a/README.md b/README.md index 5566055..f1c5689 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,4 @@ Development occurs in language-specific directories: |[Day3.hs](hs/src/Day3.hs)|[Day3.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day3.kt)|[day3.py](py/aoc2024/day3.py)|[day3.rs](rs/src/day3.rs)| |[Day4.hs](hs/src/Day4.hs)|[Day4.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day4.kt)|[day4.py](py/aoc2024/day4.py)|[day4.rs](rs/src/day4.rs)| |[Day5.hs](hs/src/Day5.hs)|[Day5.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day5.kt)|[day5.py](py/aoc2024/day5.py)|[day5.rs](rs/src/day5.rs)| -|[Day6.hs](hs/src/Day6.hs)|[Day6.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day6.kt)||| +|[Day6.hs](hs/src/Day6.hs)|[Day6.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day6.kt)|[day6.py](py/aoc2024/day6.py)|| diff --git a/py/aoc2024/day6.py b/py/aoc2024/day6.py new file mode 100644 index 0000000..6f17d9b --- /dev/null +++ b/py/aoc2024/day6.py @@ -0,0 +1,108 @@ +""" +Day 6: Guard Gallivant +""" + +from functools import partial +from multiprocessing import Pool +from typing import Generator, Iterable, Set, Tuple + +SAMPLE_INPUT = """ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... +""" + + +def _parse(data: str) -> Tuple[Tuple[int, int], Tuple[int, int], Set[Tuple[int, int]]]: + obstructions = [] + for y, line in enumerate(filter(None, data.splitlines())): + for x, char in enumerate(line): + match char: + case "^": + initial_y, initial_x = y, x + case "#": + obstructions.append((y, x)) + return (initial_y, initial_x), (y + 1, len(line)), frozenset(obstructions) + + +def _visit( + initial_pos: Tuple[int, int], + max_bounds: Tuple[int, int], + obstructions: Iterable[Tuple[int, int]], +) -> Generator[Tuple[int, int], None, Set[Tuple[int, int]]]: + y, x = initial_pos + dy, dx = -1, 0 + max_y, max_x = max_bounds + visited = set() + while True: + yield (y, x), (dy, dx) + visited.add((y, x)) + next_y, next_x = y + dy, x + dx + if next_y not in range(max_y) or next_x not in range(max_x): + break + if (next_y, next_x) in obstructions: + dy, dx = dx, -dy + else: + y, x = next_y, next_x + return visited + + +def _part1( + initial_pos: Tuple[int, int], + max_bounds: Tuple[int, int], + obstructions: Iterable[Tuple[int, int]], +) -> Set[Tuple[int, int]]: + visitor = _visit(initial_pos, max_bounds, obstructions) + while True: + try: + next(visitor) + except StopIteration as error: + return error.value + + +def part1(data: str) -> int: + """ + >>> part1(SAMPLE_INPUT) + 41 + """ + initial_pos, max_bounds, obstructions = _parse(data) + return len(_part1(initial_pos, max_bounds, obstructions)) + + +def _part2( + initial_pos: Tuple[int, int], + max_bounds: Tuple[int, int], + obstructions: Iterable[Tuple[int, int]], +) -> bool: + visited = set() + for key in _visit(initial_pos, max_bounds, obstructions): + if key in visited: + return True + visited.add(key) + return False + + +def part2(data: str, concurrency: int = None) -> int: + """ + >>> part2(SAMPLE_INPUT) + 6 + """ + initial_pos, max_bounds, obstructions = _parse(data) + candidates = _part1(initial_pos, max_bounds, obstructions) - {initial_pos} + with Pool(concurrency) as pool: + return sum( + pool.imap_unordered( + partial(_part2, initial_pos, max_bounds), + (obstructions | {candidate} for candidate in candidates), + ) + ) + + +parts = (part1, part2) diff --git a/py/poetry.lock b/py/poetry.lock index 6fe6fbe..d61e908 100644 --- a/py/poetry.lock +++ b/py/poetry.lock @@ -174,29 +174,29 @@ histogram = ["pygal", "pygaljs", "setuptools"] [[package]] name = "ruff" -version = "0.8.1" +version = "0.8.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, - {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, - {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, - {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, - {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, - {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, - {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, - {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, - {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, - {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, - {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, - {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, + {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"}, + {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"}, + {file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"}, + {file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"}, + {file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"}, + {file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"}, + {file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"}, + {file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"}, + {file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"}, + {file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"}, + {file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"}, + {file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"}, ] [[package]] @@ -241,4 +241,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.13" -content-hash = "6230e52ff6d4f5f579382af898326d4865f02e5450c601c9b7be07eb072ae459" +content-hash = "db29b0b5ed47fc0caccd56cbbfd0a34bac00b377530ce0e55ab6a6c5446af819" diff --git a/py/pyproject.toml b/py/pyproject.toml index c4e8a04..49a0b1a 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -12,7 +12,7 @@ python = "^3.13" natsort = "^8.4.0" [tool.poetry.group.dev.dependencies] -ruff = "^0.8.1" +ruff = "^0.8.2" pytest = "^8.3.4" pytest-benchmark = { version = "^5.1.0", extras = ["histogram"] } @@ -29,6 +29,7 @@ day2 = "aoc2024.day2:parts" day3 = "aoc2024.day3:parts" day4 = "aoc2024.day4:parts" day5 = "aoc2024.day5:parts" +day6 = "aoc2024.day6:parts" [build-system] requires = ["poetry-core"]