Skip to content

Commit

Permalink
day 6
Browse files Browse the repository at this point in the history
  • Loading branch information
vsedov committed Dec 6, 2024
1 parent 40702fd commit c2a8938
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 49 deletions.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ dependencies = [
"bigo>=0.2",
"big-o>=0.11.0",
"rich>=13.9.4",
"pyperf>=2.8.1",
"scalene>=1.5.49",
]
requires-python = ">=3.12"
readme = "README.md"
Expand Down
3 changes: 1 addition & 2 deletions src/aoc/aoc2024/day_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ def check_sequence(nums: np.ndarray) -> bool:
def check_removable(nums: np.ndarray) -> bool:
n = len(nums)
for i in range(n):
# check sequence validitity by eval adjacent val - skip ith
valid = True
for j in range(n - 1):
if j < i - 1:
Expand Down Expand Up @@ -70,4 +69,4 @@ def main(txt: str) -> None:

if __name__ == "__main__":
aoc = Aoc(day=get_day(), years=YEAR)
aoc.run(main, submit=True, part="both", readme_update=True)
aoc.run(main, submit=True, part="both", readme_update=True, profile=True)
82 changes: 82 additions & 0 deletions src/aoc/aoc2024/day_06.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from collections import defaultdict
from itertools import product
from typing import Set, Tuple

import more_itertools as mi
import numpy as np
import numpy.typing as npt
from numba import jit, njit
from numba.typed import List

from src.aoc.aoc2024 import YEAR, get_day
from src.aoc.aoc_helper import Aoc


class foo(Exception):
pass


def explore(mapping: dict[complex, str], start: complex) -> set[complex]:
pos, heading = start, -1j
seen = set()

grid = defaultdict(lambda: None, mapping)

while pos in mapping:
if (pos, heading) in seen:
raise foo
seen.add((pos, heading))
next_pos = pos + heading
if grid[next_pos] == "#":
heading *= 1j
next_pos = pos
pos = next_pos
return {p for p, _ in seen}


def parse_map(txt: str) -> tuple[dict[complex, str], complex]:
# Vectorized parsing using numpy
grid = np.array([list(line) for line in txt.splitlines()])
rows, cols = grid.shape

y, x = np.where(grid == "^")
start = complex(x[0], y[0])

mapping = {complex(x, y): grid[y, x] for y, x in product(range(rows), range(cols))}

return mapping, start


def part_a(txt: str) -> int:
mapping, start = parse_map(txt)
return len(explore(mapping, start))


def part_b(txt: str) -> int:
mapping, start = parse_map(txt)
path = explore(mapping, start)

path_set = set(path)
causes_loop = set()

base_mapping = defaultdict(lambda: None, mapping)

for pos in path_set:
base_mapping[pos] = "#"
try:
explore(base_mapping, start)
except foo:
causes_loop.add(pos)
base_mapping[pos] = mapping[pos] # Restore original value

return len(causes_loop)


def main(txt: str) -> None:
print("part_a: ", part_a(txt))
print("part_b: ", part_b(txt))


if __name__ == "__main__":
aoc = Aoc(day=get_day(), years=YEAR)
aoc.run(main, submit=False, part="both", readme_update=True, profile=True)
8 changes: 4 additions & 4 deletions src/aoc/aoc2024/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

| Day | Problem | Part A | Part B | Complete |
|-----|---------|---------|---------|----------|
| 01 | [Historian Hysteria](https://adventofcode.com/2024/day/1) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| 02 | [Red-Nosed Reports](https://adventofcode.com/2024/day/2) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| 03 | [Mull It Over](https://adventofcode.com/2024/day/3) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| 01 | [Historian Hysteria](https://adventofcode.com/2024/day/1) | | | |
| 02 | [Red-Nosed Reports](https://adventofcode.com/2024/day/2) | | | |
| 03 | [Mull It Over](https://adventofcode.com/2024/day/3) | | | |
| 04 | [Ceres Search](https://adventofcode.com/2024/day/4) ||||
| 05 | [Print Queue](https://adventofcode.com/2024/day/5) ||||
| 06 | [?](https://adventofcode.com/2024/day/6) | :x: | :x: | :x: |
| 06 | [Guard Gallivant](https://adventofcode.com/2024/day/6) | | | |
| 07 | [?](https://adventofcode.com/2024/day/7) | :x: | :x: | :x: |
| 08 | [?](https://adventofcode.com/2024/day/8) | :x: | :x: | :x: |
| 09 | [?](https://adventofcode.com/2024/day/9) | :x: | :x: | :x: |
Expand Down
72 changes: 29 additions & 43 deletions src/aoc/aoc_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,23 @@ def data_generator(n: int) -> str:

return data_generator

# @staticmethod
# def create_data_generator(data: str):
# """Creates an appropriate data generator based on input type"""
# # Pre-split the lines once
# lines = data.split("\n") if isinstance(data, str) else data
# total_size = len(lines)
#
# def data_generator(n: int) -> str:
# # Calculate how many lines to include based on the ratio n/total_size
# subset_size = max(1, int(n))
# subset_size = min(subset_size, total_size) # Don't exceed available data
#
# if isinstance(data, str):
# return "\n".join(lines[:subset_size])
# return lines[:subset_size]
#
# return data_generator, total_size
@staticmethod
def create_aoc_generator(data: str):
"""Creates an optimized data generator for AOC problems with big_O analysis"""
lines = data.splitlines()
base_size = len(lines)
def create_data_generator(data: str):
"""Creates an appropriate data generator based on input type"""
# Pre-split the lines once
lines = data.split("\n") if isinstance(data, str) else data
total_size = len(lines)

def generator(n: int) -> list:
# Ensure we generate meaningful test data sizes
size = min(max(1, int(n)), base_size)
def data_generator(n: int) -> str:
# Calculate how many lines to include based on the ratio n/total_size
subset_size = max(1, int(n))
subset_size = min(subset_size, total_size) # Don't exceed available data

# Return data as a list for consistent handling
return lines[:size]
if isinstance(data, str):
return "\n".join(lines[:subset_size])
return lines[:subset_size]

return generator, base_size
return data_generator, total_size

@staticmethod
def analyze(func: Callable, data: str) -> ComplexityResult:
Expand Down Expand Up @@ -167,7 +153,7 @@ def analyze_performance(
analyze_complexity: bool = True,
) -> PerformanceMetrics:
"""Run comprehensive performance analysis on a solution function"""
times = []
times = []
start_mem = self.process.memory_info().rss
peak_mem = start_mem
result = None
Expand Down Expand Up @@ -195,8 +181,8 @@ def analyze_performance(
pre_mem = self.process.memory_info().rss

# Time execution
start = timer()
result = func(self.data)
start = timer()
result = func(self.data)
end = timer()

# Track memory after run
Expand All @@ -205,20 +191,20 @@ def analyze_performance(

times.append((end - start) * 1e6) # Convert to microseconds

end_mem = self.process.memory_info().rss
end_mem = self.process.memory_info().rss
memory_used = end_mem - start_mem
peak_memory_used = peak_mem - start_mem

return PerformanceMetrics(
execution_time_us=np.mean(times),
time_std_us=np.std(times),
return PerformanceMetrics(
execution_time_us=np.mean(times),
time_std_us=np.std(times),
memory_bytes=memory_used,
memory_peak=peak_memory_used,
cpu_percent=self.process.cpu_percent(),
cpu_percent=self.process.cpu_percent(),
complexity=complexity_result,
result=result,
result=result,
profile_stats=profile_stats,
)
)

def print_metrics(self, metrics: PerformanceMetrics, part: str):
"""Display performance metrics in a nicely formatted table"""
Expand Down Expand Up @@ -323,10 +309,10 @@ def run(
console.print(
f"\n[red]Cannot proceed with part {current_part} - test failed"
)
continue
continue

# Run performance analysis if requested
if profile:
if profile:
console.rule(
f"[yellow]Performance Analysis - Part {current_part.upper()}"
)
Expand All @@ -346,10 +332,10 @@ def run(
self.submit(result, part=current_part)
console.print(f"[green]✓ Part {current_part} submitted")

if readme_update:
with console.status("[blue]Updating README..."):
self.update_readme()
console.print("[green]✓ README updated")
if readme_update:
with console.status("[blue]Updating README..."):
self.update_readme()
console.print("[green]✓ README updated")

def submit(self, answer, part=None) -> None:
"""Submit an answer to Advent of Code"""
Expand Down
24 changes: 24 additions & 0 deletions tests/aoc2024/2024_day_06_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

from src.aoc.aoc2024 import day_06 as d

TEST_INPUT = """
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...
""".strip()


def test_a() -> None:
assert d.part_a(TEST_INPUT) == 41


def test_b() -> None:
assert d.part_b(TEST_INPUT) == 6
Loading

0 comments on commit c2a8938

Please sign in to comment.