Skip to content

Commit

Permalink
AoC 2024 day 12 part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
loociano committed Dec 12, 2024
1 parent bed21a7 commit 697ce2e
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 0 deletions.
Empty file added aoc2024/src/day1/__init__.py
Empty file.
Empty file.
49 changes: 49 additions & 0 deletions aoc2024/src/day1/python/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cache


def _has_even_num_digits(number: int) -> bool:
"""Returns true if an integer has an even number of digits.
Examples: 1234 is True and 123 is False."""
return len(str(number)) % 2 == 0


class Simulation:
def __init__(self, initial_state: str):
self._roots = tuple([int(stone) for stone in initial_state.split()])

@cache
def _iterate(self, value: int, num_iterations: int = 0) -> int:
"""Returns the number of stones after iteration."""
if num_iterations == 0:
return 1 # Leaf node
if value == 0:
return self._iterate(1, num_iterations - 1)
elif _has_even_num_digits(value):
half_digits = len(str(value)) // 2
# Break stone into 2:
return (self._iterate(int(str(value)[:half_digits]), num_iterations - 1)
+ self._iterate(int(str(value)[half_digits:]), num_iterations - 1))
else:
return self._iterate(value * 2024, num_iterations - 1)

def simulate(self, num_iterations=0) -> int:
"""Simulates the rules of stones a given number of times."""
return sum(self._iterate(root, num_iterations) for root in self._roots)


def count_stones(initial_state: str, blinks: int = 0) -> int:
"""Counts the number of stones after a number of blinks."""
return Simulation(initial_state).simulate(num_iterations=blinks)
Empty file added aoc2024/src/day12/__init__.py
Empty file.
Empty file.
54 changes: 54 additions & 0 deletions aoc2024/src/day12/python/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Sequence
from collections import defaultdict

type Pos = tuple[int, int] # (y,x)


def _dfs_helper(pos: Pos, flower: str, garden_map: Sequence[str],
visited: set[Pos], area: list[int], perimeter: list[int]):
if (pos[0] < 0 or pos[0] > len(garden_map) - 1
or pos[1] < 0 or pos[1] > len(garden_map[0]) - 1):
# Out of bounds.
perimeter[0] += 1
visited.add(pos)
return
if garden_map[pos[0]][pos[1]] != flower:
# Facing another region.
perimeter[0] += 1
return
# Same region.
area[0] += 1
visited.add(pos)
for direction in ((-1, 0), (0, 1), (1, 0), (0, -1)):
next_pos = (pos[0] + direction[0], pos[1] + direction[1])
if next_pos not in visited:
_dfs_helper(next_pos, flower, garden_map, visited, area, perimeter)


def calculate_fencing_price(garden_map: Sequence[str]) -> int:
"""Calculates the price to fence all regions.
A region price is calculated as its area multiplied by its perimeter."""
visited: dict[str, set[Pos]] = defaultdict(set) # Tracks region positions.
total_price = 0
for y in range(len(garden_map)):
for x in range(len(garden_map[0])):
flower = garden_map[y][x]
pos = (y, x)
if pos not in visited[flower]:
area, perimeter = [0], [0] # Pass by reference
_dfs_helper(pos, garden_map[y][x], garden_map, visited[flower], area, perimeter)
total_price += area[0] * perimeter[0]
return total_price
Empty file added aoc2024/test/day12/__init__.py
Empty file.
Empty file.
4 changes: 4 additions & 0 deletions aoc2024/test/day12/python/example1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
AAAA
BBCD
BBCC
EEEC
5 changes: 5 additions & 0 deletions aoc2024/test/day12/python/example2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OOOOO
OXOXO
OOOOO
OXOXO
OOOOO
10 changes: 10 additions & 0 deletions aoc2024/test/day12/python/example3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
140 changes: 140 additions & 0 deletions aoc2024/test/day12/python/input.txt

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions aoc2024/test/day12/python/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest

from common.python3.AdventOfCodeTestCase import AdventOfCodeTestCase
from aoc2024.src.day12.python.solution import calculate_fencing_price


class TestSolution(AdventOfCodeTestCase):
def __init__(self, *args, **kwargs):
(super(TestSolution, self).__init__(__file__, *args,
**kwargs))

def test_part1_with4Areas_calculates(self):
self.assertEqual(140, calculate_fencing_price(self.examples[0]))

def test_part1_withAreaHasHoles_calculates(self):
self.assertEqual(772, calculate_fencing_price(self.examples[1]))

def test_part1_withLargerExample_calculates(self):
self.assertEqual(1930, calculate_fencing_price(self.examples[2]))

def test_part1_withPuzzleInput_calculates(self):
self.assertEqual(1465112, calculate_fencing_price(self.input))


if __name__ == '__main__':
unittest.main()

0 comments on commit 697ce2e

Please sign in to comment.