Solved 2024/10 P1+P2 and added BFS to helper file
This commit is contained in:
parent
e7a1efc09a
commit
5cc0f4fabf
@ -100,7 +100,86 @@ The reindeer gleefully carries over a protractor and adds it to the
|
|||||||
pile. *What is the sum of the scores of all trailheads on your
|
pile. *What is the sum of the scores of all trailheads on your
|
||||||
topographic map?*
|
topographic map?*
|
||||||
|
|
||||||
To begin, [get your puzzle input](10/input).
|
Your puzzle answer was `754`.
|
||||||
|
|
||||||
Answer:
|
## \-\-- Part Two \-\-- {#part2}
|
||||||
|
|
||||||
|
The reindeer spends a few minutes reviewing your hiking trail map before
|
||||||
|
realizing something, disappearing for a few minutes, and finally
|
||||||
|
returning with yet another slightly-charred piece of paper.
|
||||||
|
|
||||||
|
The paper describes a second way to measure a trailhead called its
|
||||||
|
*rating*. A trailhead\'s rating is the *number of distinct hiking
|
||||||
|
trails* which begin at that trailhead. For example:
|
||||||
|
|
||||||
|
.....0.
|
||||||
|
..4321.
|
||||||
|
..5..2.
|
||||||
|
..6543.
|
||||||
|
..7..4.
|
||||||
|
..8765.
|
||||||
|
..9....
|
||||||
|
|
||||||
|
The above map has a single trailhead; its rating is `3` because there
|
||||||
|
are exactly three distinct hiking trails which begin at that position:
|
||||||
|
|
||||||
|
.....0. .....0. .....0.
|
||||||
|
..4321. .....1. .....1.
|
||||||
|
..5.... .....2. .....2.
|
||||||
|
..6.... ..6543. .....3.
|
||||||
|
..7.... ..7.... .....4.
|
||||||
|
..8.... ..8.... ..8765.
|
||||||
|
..9.... ..9.... ..9....
|
||||||
|
|
||||||
|
Here is a map containing a single trailhead with rating `13`:
|
||||||
|
|
||||||
|
..90..9
|
||||||
|
...1.98
|
||||||
|
...2..7
|
||||||
|
6543456
|
||||||
|
765.987
|
||||||
|
876....
|
||||||
|
987....
|
||||||
|
|
||||||
|
This map contains a single trailhead with rating `227` (because there
|
||||||
|
are `121` distinct hiking trails that lead to the `9` on the right edge
|
||||||
|
and `106` that lead to the `9` on the bottom edge):
|
||||||
|
|
||||||
|
012345
|
||||||
|
123456
|
||||||
|
234567
|
||||||
|
345678
|
||||||
|
4.6789
|
||||||
|
56789.
|
||||||
|
|
||||||
|
Here\'s the larger example from before:
|
||||||
|
|
||||||
|
89010123
|
||||||
|
78121874
|
||||||
|
87430965
|
||||||
|
96549874
|
||||||
|
45678903
|
||||||
|
32019012
|
||||||
|
01329801
|
||||||
|
10456732
|
||||||
|
|
||||||
|
Considering its trailheads in reading order, they have ratings of `20`,
|
||||||
|
`24`, `10`, `4`, `1`, `4`, `5`, `8`, and `5`. The sum of all trailhead
|
||||||
|
ratings in this larger example topographic map is `81`.
|
||||||
|
|
||||||
|
You\'re not sure how, but the reindeer seems to have crafted some tiny
|
||||||
|
flags out of toothpicks and bits of paper and is using them to mark
|
||||||
|
trailheads on your topographic map. *What is the sum of the ratings of
|
||||||
|
all trailheads?*
|
||||||
|
|
||||||
|
Your puzzle answer was `1609`.
|
||||||
|
|
||||||
|
Both parts of this puzzle are complete! They provide two gold stars:
|
||||||
|
\*\*
|
||||||
|
|
||||||
|
At this point, you should [return to your Advent calendar](/2024) and
|
||||||
|
try another puzzle.
|
||||||
|
|
||||||
|
If you still want to see it, you can [get your puzzle
|
||||||
|
input](10/input).
|
||||||
|
|
||||||
|
16
2024/10/README.md
Normal file
16
2024/10/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
This calls for some explanations.
|
||||||
|
solutions_P1.py solves the first part and solutions_P2.py solves the second part.
|
||||||
|
|
||||||
|
I first started out writing the code in solutions_P2.py but i kept getting the wrong answers.
|
||||||
|
Basically I find a zero in the grid, then check up, down, left and right if any of them are
|
||||||
|
a one. If any are, call find_path, which looks for two, then three and so on.
|
||||||
|
Returns 1 each time a nine is found. This means all valid paths are returned.
|
||||||
|
|
||||||
|
I decided to read the problem again and started from a fresh solutions_P1.py file.
|
||||||
|
The problem can be solved using BFS (Breadth First Search). I implemented a standard BFS
|
||||||
|
function in my fred.py helper file.
|
||||||
|
Then used that to corrently find the solution.
|
||||||
|
|
||||||
|
Reading part 2 i realized my first code would be useful and without modifying it, it gave me
|
||||||
|
the right answer.
|
@ -1,31 +0,0 @@
|
|||||||
#!/bin/python3
|
|
||||||
import sys,time,re
|
|
||||||
from pprint import pprint
|
|
||||||
sys.path.insert(0, '../../')
|
|
||||||
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid,TSP,dijkstra
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
input_f = 'test'
|
|
||||||
|
|
||||||
part = 1
|
|
||||||
#########################################
|
|
||||||
# #
|
|
||||||
# Part 1 #
|
|
||||||
# #
|
|
||||||
#########################################
|
|
||||||
|
|
||||||
if part == 1:
|
|
||||||
grid = toGrid(input_f,True)
|
|
||||||
nprint(grid)
|
|
||||||
|
|
||||||
print(dijkstra(grid,0,9))
|
|
||||||
|
|
||||||
#########################################
|
|
||||||
# #
|
|
||||||
# Part 2 #
|
|
||||||
# #
|
|
||||||
#########################################
|
|
||||||
if part == 2:
|
|
||||||
exit()
|
|
||||||
|
|
||||||
print("--- %s seconds ---" % (time.time() - start_time))
|
|
67
2024/10/solution_P1.py
Normal file
67
2024/10/solution_P1.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
import sys,time,re
|
||||||
|
from pprint import pprint
|
||||||
|
sys.path.insert(0, '../../')
|
||||||
|
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid,TSP,dijkstra,bfs,get_value_in_direction,addTuples
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
input_f = 'input'
|
||||||
|
|
||||||
|
grid = toGrid(input_f,list2int)
|
||||||
|
|
||||||
|
directions = [(-1,0),(1,0),(0,1),(0,-1)]
|
||||||
|
|
||||||
|
starts = []
|
||||||
|
ends = []
|
||||||
|
|
||||||
|
# Get coordinates of all start points (0) and all end points (9)
|
||||||
|
for row in range(len(grid)):
|
||||||
|
for col in range(len(grid[row])):
|
||||||
|
if grid[row][col] == 0:
|
||||||
|
starts.append((row,col))
|
||||||
|
elif grid[row][col] == 9:
|
||||||
|
ends.append((row,col))
|
||||||
|
|
||||||
|
# If the current node is in the list of end nodes, return it.
|
||||||
|
def is_goal(node):
|
||||||
|
return node in ends
|
||||||
|
|
||||||
|
# Get the neighbors of the current node.
|
||||||
|
def get_neighbors(node):
|
||||||
|
directions = ['up','down','left','right']
|
||||||
|
offsets = {
|
||||||
|
'up': (-1, 0),
|
||||||
|
'down': (1, 0),
|
||||||
|
'left': (0, -1),
|
||||||
|
'right': (0, 1),
|
||||||
|
}
|
||||||
|
neighbors = []
|
||||||
|
|
||||||
|
# Loop through all the directions
|
||||||
|
for d in directions:
|
||||||
|
t = get_value_in_direction(grid,node)
|
||||||
|
# If the direction is 1 more than the current nodes value,
|
||||||
|
# add it to the list of valid neighbors.
|
||||||
|
if get_value_in_direction(grid,node,d) == t+1:
|
||||||
|
neighbors.append(addTuples(offsets[d],node))
|
||||||
|
# Return the list of valid neighbors
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
# Loop through all the starting points.
|
||||||
|
for s in starts:
|
||||||
|
# Call a standard BFS (Breadth First Search).
|
||||||
|
# Takes the following inputs:
|
||||||
|
# s: Starting node
|
||||||
|
# is_goal: function that returns true if the current node is the goal
|
||||||
|
# get_neighbors: returns a list of valid neighbors
|
||||||
|
# Returns a list of all visited goals and the path to the goals.
|
||||||
|
goal_nodes, paths_to_goal = bfs(s,is_goal,get_neighbors)
|
||||||
|
|
||||||
|
# We onlu care about how many goals each starting position reached.
|
||||||
|
result += len(goal_nodes)
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
print("--- %s seconds ---" % (time.time() - start_time))
|
44
2024/10/solution_P2.py
Normal file
44
2024/10/solution_P2.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
import sys,time,re
|
||||||
|
from pprint import pprint
|
||||||
|
sys.path.insert(0, '../../')
|
||||||
|
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid, get_value_in_direction,addTuples,grid_valid
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
input_f = 'input'
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
dir = {
|
||||||
|
'up': (-1, 0),
|
||||||
|
'down': (1, 0),
|
||||||
|
'left': (0, -1),
|
||||||
|
'right': (0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
grid = toGrid(input_f,list2int)
|
||||||
|
|
||||||
|
def find_path(grid:list,pos:set,cur:set):
|
||||||
|
path = 0
|
||||||
|
if cur == 9:
|
||||||
|
return 1
|
||||||
|
cur+=1
|
||||||
|
for direction in ['up', 'down', 'left', 'right']:
|
||||||
|
next_pos = addTuples(pos, dir[direction])
|
||||||
|
if get_value_in_direction(grid,pos,direction)== cur:
|
||||||
|
path += find_path(grid, next_pos, cur)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
for r, row in enumerate(grid):
|
||||||
|
for c, col in enumerate(row):
|
||||||
|
if grid[r][c] == 0:
|
||||||
|
for direction in ['up', 'down', 'left', 'right']:
|
||||||
|
next_pos = addTuples((r,c), dir[direction])
|
||||||
|
if get_value_in_direction(grid,(r,c),direction) == 1:
|
||||||
|
result += find_path(grid,next_pos,1)
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("--- %s seconds ---" % (time.time() - start_time))
|
@ -1,162 +0,0 @@
|
|||||||
#!/bin/python3
|
|
||||||
import sys,time,re
|
|
||||||
from pprint import pprint
|
|
||||||
sys.path.insert(0, '../../')
|
|
||||||
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid, get_value_in_direction,addTuples
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
input_f = 'test2'
|
|
||||||
|
|
||||||
part = 1
|
|
||||||
#########################################
|
|
||||||
# #
|
|
||||||
# Part 1 #
|
|
||||||
# #
|
|
||||||
#########################################
|
|
||||||
|
|
||||||
if part == 1:
|
|
||||||
|
|
||||||
|
|
||||||
def get_value_in_direction(grid, position, direction=None, length=1, type: str = None):
|
|
||||||
"""
|
|
||||||
Get the value(s) in a specified direction from a given position in a grid.
|
|
||||||
If no direction is provided, returns the value at the current position.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
grid (list of list of int/float/str): The 2D grid.
|
|
||||||
position (set): A set containing x (row index) and y (column index) as integers.
|
|
||||||
direction (str, optional): The direction to check. Defaults to None.
|
|
||||||
length (int, optional): The number of steps to check in the given direction. Default is 1.
|
|
||||||
type (str, optional): The type of result to return ('list' or 'str'). Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list or str: A list or string of values in the specified direction, or a single value.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If direction is invalid or position is not a set of two integers.
|
|
||||||
TypeError: If grid is not a list of lists.
|
|
||||||
"""
|
|
||||||
if not all(isinstance(row, list) for row in grid):
|
|
||||||
raise TypeError("Grid must be a list of lists.")
|
|
||||||
|
|
||||||
# Ensure position is a set of two integers
|
|
||||||
if len(position) != 2 or not all(isinstance(coord, int) for coord in position):
|
|
||||||
raise ValueError("Position must be a set containing two integers (x, y).")
|
|
||||||
|
|
||||||
x, y = position
|
|
||||||
offsets = {
|
|
||||||
'up': (-1, 0),
|
|
||||||
'down': (1, 0),
|
|
||||||
'left': (0, -1),
|
|
||||||
'right': (0, 1),
|
|
||||||
'up-left': (-1, -1),
|
|
||||||
'up-right': (-1, 1),
|
|
||||||
'down-left': (1, -1),
|
|
||||||
'down-right': (1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
# If no direction is given, return the value at the current position
|
|
||||||
if direction is None:
|
|
||||||
if 0 <= x < len(grid) and 0 <= y < len(grid[x]):
|
|
||||||
return grid[x][y]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Validate direction
|
|
||||||
if direction not in offsets:
|
|
||||||
raise ValueError(f"Invalid direction: {direction}. Choose from {list(offsets.keys())}")
|
|
||||||
|
|
||||||
dx, dy = offsets[direction]
|
|
||||||
new_x, new_y = x + dx, y + dy
|
|
||||||
|
|
||||||
values = []
|
|
||||||
|
|
||||||
if length == 1:
|
|
||||||
# Check for out-of-bounds
|
|
||||||
if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[new_x]):
|
|
||||||
return grid[new_x][new_y]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
for step in range(length):
|
|
||||||
new_x, new_y = x + step * dx, y + step * dy
|
|
||||||
# Check for out-of-bounds
|
|
||||||
if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[new_x]):
|
|
||||||
values.append(grid[new_x][new_y])
|
|
||||||
else:
|
|
||||||
return [] # Return empty list if any position is out of bounds
|
|
||||||
if type == 'list':
|
|
||||||
return values
|
|
||||||
elif type == 'str':
|
|
||||||
return ''.join(values)
|
|
||||||
else:
|
|
||||||
return values
|
|
||||||
|
|
||||||
grid = toGrid(input_f,True)
|
|
||||||
|
|
||||||
def extract_tuples(nested_list):
|
|
||||||
result = []
|
|
||||||
for item in nested_list:
|
|
||||||
if isinstance(item, tuple): # Check if the item is a tuple
|
|
||||||
result.append(item)
|
|
||||||
elif isinstance(item, list): # If it's a list, recurse into it
|
|
||||||
result.extend(extract_tuples(item))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def find_path(grid,pos:set,cur):
|
|
||||||
path = 0
|
|
||||||
if cur == 9:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
cur+=1
|
|
||||||
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,pos,'up') == cur:
|
|
||||||
path += find_path(grid,addTuples(pos,(-1,0)),cur)
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,pos,'down') == cur:
|
|
||||||
path += find_path(grid,addTuples(pos,(1,0)),cur)
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,pos,'left') == cur:
|
|
||||||
path += find_path(grid,addTuples(pos,(0,-1)),cur)
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,pos,'right') == cur:
|
|
||||||
path += find_path(grid,addTuples(pos,(0,1)),cur)
|
|
||||||
|
|
||||||
|
|
||||||
return path
|
|
||||||
|
|
||||||
result = 0
|
|
||||||
|
|
||||||
for r, row in enumerate(grid):
|
|
||||||
for c, col in enumerate(row):
|
|
||||||
#get_value_in_direction(grid, position, direction=None, length=1, type: str = None):
|
|
||||||
if grid[r][c] == 0:
|
|
||||||
if get_value_in_direction(grid,(r,c),'up') == 1:
|
|
||||||
#print(find_path(grid,(r-1,c),1))
|
|
||||||
result += find_path(grid,(r-1,c),1)
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,(r,c),'down') == 1:
|
|
||||||
result += find_path(grid,(r+1,c),1)
|
|
||||||
#print(find_path(grid,(r+1,c),1,0))
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,(r,c),'left') == 1:
|
|
||||||
result += find_path(grid,(r,c-1),1)
|
|
||||||
#print(find_path(grid,(r,c-1),1,0))
|
|
||||||
|
|
||||||
if get_value_in_direction(grid,(r,c),'right') == 1:
|
|
||||||
result += find_path(grid,(r,c+1),1)
|
|
||||||
#print(find_path(grid,(r,c+1),1,0))
|
|
||||||
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
|
|
||||||
#########################################
|
|
||||||
# #
|
|
||||||
# Part 2 #
|
|
||||||
# #
|
|
||||||
#########################################
|
|
||||||
if part == 2:
|
|
||||||
exit()
|
|
||||||
|
|
||||||
print("--- %s seconds ---" % (time.time() - start_time))
|
|
@ -1,7 +1,7 @@
|
|||||||
# AdventOfCode
|
# AdventOfCode
|
||||||
|
|
||||||
## 2024
|
## 2024
|
||||||
|
.-----. .------------------.
|
||||||
.--'~ ~ ~| .-' * \ / '-. 1 **
|
.--'~ ~ ~| .-' * \ / '-. 1 **
|
||||||
.--'~ ,* ~ | | >o< \_\_\|_/__/ | 2 **
|
.--'~ ,* ~ | | >o< \_\_\|_/__/ | 2 **
|
||||||
.---': ~ '(~), ~| | >@>O< o-_/.()__------| 3 **
|
.---': ~ '(~), ~| | >@>O< o-_/.()__------| 3 **
|
||||||
@ -11,6 +11,7 @@
|
|||||||
|#~~~@@@@ @ | |/\ ''. | | -/ :| 7 **
|
|#~~~@@@@ @ | |/\ ''. | | -/ :| 7 **
|
||||||
|~~..--. _____ | |* /~\ '.| | - / .'| 8 **
|
|~~..--. _____ | |* /~\ '.| | - / .'| 8 **
|
||||||
'---' ||[][]_\-| |~/ * \ :| | *..' | 9 **
|
'---' ||[][]_\-| |~/ * \ :| | *..' | 9 **
|
||||||
|
|------- | | /\ .'| |'''~~~~~| 10 **
|
||||||
|
|
||||||
## 2023
|
## 2023
|
||||||
|
|
||||||
|
Binary file not shown.
49
fred.py
49
fred.py
@ -71,7 +71,7 @@ def toGrid(input, parser=None):
|
|||||||
try:
|
try:
|
||||||
with open(input) as file:
|
with open(input) as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
grid.append(list2int(list(line.rstrip())) if parser else list(line.rstrip())) # Use parser or default processing
|
grid.append(parser(list(line.rstrip())) if parser else list(line.rstrip())) # Use parser or default processing
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise FileNotFoundError(f"The file '{input}' was not found.")
|
raise FileNotFoundError(f"The file '{input}' was not found.")
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
@ -540,3 +540,50 @@ def TSP(graph, start, path_type='shortest'):
|
|||||||
print(f"No valid path found starting at '{start}'. Ensure all cities are connected.")
|
print(f"No valid path found starting at '{start}'. Ensure all cities are connected.")
|
||||||
|
|
||||||
return best_path, best_distance if best_path else None
|
return best_path, best_distance if best_path else None
|
||||||
|
|
||||||
|
def bfs(start, is_goal, get_neighbors, max_depth=float('inf')):
|
||||||
|
"""
|
||||||
|
Generic Breadth-First Search (BFS) function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start: The starting node/state
|
||||||
|
is_goal: A function that checks if the current node is a goal state
|
||||||
|
get_neighbors: A function that returns valid neighboring nodes/states
|
||||||
|
max_depth: Maximum search depth to prevent infinite loops (default: no limit)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (goal_nodes, paths_to_goal)
|
||||||
|
- goal_nodes: List of nodes that satisfy the goal condition
|
||||||
|
- paths_to_goal: Dictionary mapping goal nodes to their paths from start
|
||||||
|
"""
|
||||||
|
# Queue for BFS: each element is (current_node, path_to_node, depth)
|
||||||
|
queue = [(start, [start], 0)]
|
||||||
|
|
||||||
|
# Track visited nodes to prevent cycles
|
||||||
|
visited = set([start])
|
||||||
|
|
||||||
|
# Store goal nodes and their paths
|
||||||
|
goal_nodes = []
|
||||||
|
paths_to_goal = {}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current, path, depth = queue.pop(0)
|
||||||
|
|
||||||
|
# Check if current node is a goal
|
||||||
|
if is_goal(current):
|
||||||
|
goal_nodes.append(current)
|
||||||
|
paths_to_goal[current] = path
|
||||||
|
|
||||||
|
# Stop if max depth reached
|
||||||
|
if depth >= max_depth:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Explore neighbors
|
||||||
|
for neighbor in get_neighbors(current):
|
||||||
|
# Prevent revisiting nodes
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
new_path = path + [neighbor]
|
||||||
|
queue.append((neighbor, new_path, depth + 1))
|
||||||
|
|
||||||
|
return goal_nodes, paths_to_goal
|
Loading…
Reference in New Issue
Block a user