diff --git a/2024/10/10.md b/2024/10/10.md index 3c2e6aa..393c0f0 100644 --- a/2024/10/10.md +++ b/2024/10/10.md @@ -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). diff --git a/2024/10/README.md b/2024/10/README.md new file mode 100644 index 0000000..16c39d0 --- /dev/null +++ b/2024/10/README.md @@ -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. \ No newline at end of file diff --git a/2024/10/solution.py b/2024/10/solution.py deleted file mode 100644 index 61e99a5..0000000 --- a/2024/10/solution.py +++ /dev/null @@ -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)) \ No newline at end of file diff --git a/2024/10/solution_P1.py b/2024/10/solution_P1.py new file mode 100644 index 0000000..4f21c5c --- /dev/null +++ b/2024/10/solution_P1.py @@ -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)) \ No newline at end of file diff --git a/2024/10/solution_P2.py b/2024/10/solution_P2.py new file mode 100644 index 0000000..79834f9 --- /dev/null +++ b/2024/10/solution_P2.py @@ -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)) diff --git a/2024/10/solution_old.py b/2024/10/solution_old.py deleted file mode 100644 index d584859..0000000 --- a/2024/10/solution_old.py +++ /dev/null @@ -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)) diff --git a/README.md b/README.md index 88901dd..b4a5f6c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # AdventOfCode ## 2024 - + .-----. .------------------. .--'~ ~ ~| .-' * \ / '-. 1 ** .--'~ ,* ~ | | >o< \_\_\|_/__/ | 2 ** .---': ~ '(~), ~| | >@>O< o-_/.()__------| 3 ** @@ -11,6 +11,7 @@ |#~~~@@@@ @ | |/\ ''. | | -/ :| 7 ** |~~..--. _____ | |* /~\ '.| | - / .'| 8 ** '---' ||[][]_\-| |~/ * \ :| | *..' | 9 ** + |------- | | /\ .'| |'''~~~~~| 10 ** ## 2023 diff --git a/__pycache__/fred.cpython-311.pyc b/__pycache__/fred.cpython-311.pyc index 3e4a8d8..af47874 100644 Binary files a/__pycache__/fred.cpython-311.pyc and b/__pycache__/fred.cpython-311.pyc differ diff --git a/fred.py b/fred.py index f291ac6..3112599 100644 --- a/fred.py +++ b/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 \ No newline at end of file + 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 \ No newline at end of file