Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions puzzles/solutions/2022/d08/p1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import itertools
import sys
from typing import Sequence


MAX_TREE_HEIGHT = 10


def get_trees_map(input_text: str) -> tuple[tuple[int, ...], ...]:
"""
:param input_text: puzzle input
:return: tuple of tuples representing the map of the tress
"""
return tuple(tuple(map(int, row)) for row in input_text.splitlines())


def get_adjacent_trees(
trees_map: Sequence[Sequence[int]],
map_length: int,
map_width: int,
tree_row: int,
tree_column: int,
) -> tuple[tuple[int, ...], ...]:
"""
:param trees_map: map of the trees
:param map_length: length of the map
:param map_width: width of the map
:param tree_row: row number the tree is on
:param tree_column: column number the tree is on
:return: adjacent trees of the tree which is on the given coordinates, without the tree itself
"""
column = [trees_map[row][tree_column] for row in range(map_length)]
top = tuple(column[:tree_row])
bottom = tuple(column[tree_row + 1 :])
row = [trees_map[tree_row][column] for column in range(map_width)]
left = tuple(row[:tree_column])
right = tuple(row[tree_column + 1 :])
return top, bottom, left, right


def is_tree_visible(
tree_height: int, adjacent_trees: tuple[tuple[int, ...], ...]
) -> bool:
return any(
tree_height
> max(
current_adjacent_trees or (MAX_TREE_HEIGHT,)
) # `max` raises a `ValueError` if given an empty iterator.
for current_adjacent_trees in adjacent_trees
)


def get_edge_trees_amount(map_length: int, map_width: int) -> int:
"""
:param map_length: length of the map
:param map_width: width of the map
:return: amount of trees on the edges of the map
"""
return map_length * 2 + (map_width - 2) * 2


def get_answer(input_text: str):
trees_map = get_trees_map(input_text)
map_length = len(trees_map)
map_width = len(trees_map[0])

visible_tress = 0
for row, column in itertools.product(
range(1, map_length - 1), range(1, map_width - 1)
):
adjacent_trees = get_adjacent_trees(
trees_map, map_length, map_width, row, column
)
tree_height = trees_map[row][column]
if is_tree_visible(tree_height, adjacent_trees):
visible_tress += 1
return visible_tress + get_edge_trees_amount(map_length, map_width)


if __name__ == "__main__":
try:
print(get_answer(sys.argv[1]))
except IndexError:
pass # Don't crash if no input was passed through command line arguments.
73 changes: 73 additions & 0 deletions puzzles/solutions/2022/d08/p2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import itertools
import sys
from typing import Sequence
import p1


def get_adjacent_trees(
trees_map: Sequence[Sequence[int]],
map_length: int,
map_width: int,
tree_row: int,
tree_column: int,
) -> tuple[tuple[int, ...], ...]:
"""
Return adjacent trees of the tree which is on the given coordinates, without the tree itself,
and starting with the tree closest to the given tree outwards.
:param trees_map: map of the trees
:param map_length: length of the map
:param map_width: width of the map
:param tree_row: row number the tree is on
:param tree_column: column number the tree is on
:return: adjacent trees of the tree which is on the given coordinates
"""
top, bottom, left, right = p1.get_adjacent_trees(
trees_map, map_length, map_width, tree_row, tree_column
)
return tuple(reversed(top)), bottom, tuple(reversed(left)), right


def get_tree_scenic_score(
tree_height: int, adjacent_trees: Sequence[Sequence[int]]
) -> int:
"""
:param tree_height: height of the tree
:param adjacent_trees: sequences of adjacent trees
:return: the tree's scenic score
"""
scenic_score = 1
for trees_run in adjacent_trees:
trees_until_blocking_tree = len(
tuple(itertools.takewhile(lambda tree: tree < tree_height, trees_run))
)
# Blocking trees are seen, and because `takewhile` stops before the blocking tree, we add a tree
# to the viewing distance, but only if we haven't already reached the end of the line.
if trees_until_blocking_tree < len(trees_run):
trees_until_blocking_tree += 1
scenic_score *= trees_until_blocking_tree
return scenic_score


def get_answer(input_text: str):
"""Return the highest scenic score possible for any tree."""
trees_map = p1.get_trees_map(input_text)
map_length = len(trees_map)
map_width = len(trees_map[0])

highest_scenic_score = 0
for row, column in itertools.product(range(map_length), range(map_width)):
tree_height = trees_map[row][column]
adjacent_trees = get_adjacent_trees(
trees_map, map_length, map_width, row, column
)
scenic_score = get_tree_scenic_score(tree_height, adjacent_trees)
highest_scenic_score = max(highest_scenic_score, scenic_score)

return highest_scenic_score


if __name__ == "__main__":
try:
print(get_answer(sys.argv[1]))
except IndexError:
pass # Don't crash if no input was passed through command line arguments.