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
|
||||
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
|
||||
|
||||
## 2024
|
||||
|
||||
.-----. .------------------.
|
||||
.--'~ ~ ~| .-' * \ / '-. 1 **
|
||||
.--'~ ,* ~ | | >o< \_\_\|_/__/ | 2 **
|
||||
.---': ~ '(~), ~| | >@>O< o-_/.()__------| 3 **
|
||||
@ -11,6 +11,7 @@
|
||||
|#~~~@@@@ @ | |/\ ''. | | -/ :| 7 **
|
||||
|~~..--. _____ | |* /~\ '.| | - / .'| 8 **
|
||||
'---' ||[][]_\-| |~/ * \ :| | *..' | 9 **
|
||||
|------- | | /\ .'| |'''~~~~~| 10 **
|
||||
|
||||
## 2023
|
||||
|
||||
|
Binary file not shown.
51
fred.py
51
fred.py
@ -71,7 +71,7 @@ def toGrid(input, parser=None):
|
||||
try:
|
||||
with open(input) as 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:
|
||||
raise FileNotFoundError(f"The file '{input}' was not found.")
|
||||
except IOError as e:
|
||||
@ -539,4 +539,51 @@ def TSP(graph, start, path_type='shortest'):
|
||||
if not best_path:
|
||||
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