From 5cc0f4fabfb67ce007312c6bafe37e166fa85bb5 Mon Sep 17 00:00:00 2001 From: FrederikBaerentsen Date: Tue, 10 Dec 2024 19:29:10 +0100 Subject: [PATCH] Solved 2024/10 P1+P2 and added BFS to helper file --- 2024/10/10.md | 83 +++++++++++++++- 2024/10/README.md | 16 +++ 2024/10/solution.py | 31 ------ 2024/10/solution_P1.py | 67 +++++++++++++ 2024/10/solution_P2.py | 44 +++++++++ 2024/10/solution_old.py | 162 ------------------------------- README.md | 3 +- __pycache__/fred.cpython-311.pyc | Bin 23869 -> 25353 bytes fred.py | 51 +++++++++- 9 files changed, 259 insertions(+), 198 deletions(-) create mode 100644 2024/10/README.md delete mode 100644 2024/10/solution.py create mode 100644 2024/10/solution_P1.py create mode 100644 2024/10/solution_P2.py delete mode 100644 2024/10/solution_old.py 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 3e4a8d854e841f3b5d3ff2cb38da70d4f9a96b9b..af478742f1b89a543d196447415f811cc6f9fcfb 100644 GIT binary patch delta 3245 zcmaJ@YfxLq6~23~B=iKCrwjtF010G+jEH%Z0)AoeqsVn}{QxIKMpx3Ug(UC2GI4rU z_~A~R8P6Ddf0#6xm}DldTe~%`^23u(f5e%zb<#GH%S@?eGLuaEBQu@OkWMn=>7RDb z3S*$DHPWZEyJydy^PSy2`Yv!*I8C=8z$j@q z`%1p4f7a|nljN?sv&{H(RuhNI5vQ7nop+b9&^=?W(D2^ z#-HMsO#X`F_iP$iue4r%{P^+u<~o^tRXp2W%8@;uy$0d^UhCdc?twwK*Ie|#EP!l7 zAQv;a)U&Ttc+gX^&nA3e)9)+I+%BmU8r{H;2cQ{v5h#eFh|B1IUZ+e4lX!dP@1-B` zhBnagkzZ8Qpic6zB8*xy?Uj#FMLYEK0DgcDfSsh@QB&SS#UDbm3!oLim5DhVd^Ko# zQC>fpNX1pJkmr5vxQ>mJH>=e;Q1$9?74$d&wo=FoilllGc9Hg)YP5?St{J5++^G4O zM}6dMorYS;7j+lvcY}T@1qFP15{V&{#ByB4fy`|E17tf$hrB#yAsrz{Tn==UL|q-! z-s`T<`KmrToELCndc6%iOi0ryy2?MB`fOgR!FH8bj~hvsyBVDzFSteYBAIdjRR{hP zAF3n|{Z-^+*I{Ba+emAFNoK|KJLEhDbH5L8oWiCh{@mi}I0<8nu2Ue513)m8lG7jq z8@L((+@O>+lI`AVdPIi23Ju{sZ$O9q#N>1OpL?QC$y0IOB*jxx5)P^on~50>MYs{P zLI4su?rTHmG8cU!qL$v-aR=1^NeUeM)lFfH$%@(mWU3_MKS0$M{kQpNM>V~(d2>`O zAYolVEEatsCq8hdkz{w?r+l|pZq2;i*}$W-2uGib+=Nq5JBAxwmbuvk=)H%q_ z-Y#n6)83|@2pEl02y7rS2HlK_)y1cvBMy)NNRn@T`+WmThyPc7dgHSrbqzjO_6_;F zwsX7{Jf@qD?P|y#mDxt4ryzktoA!Zg&_|s6!S&JohiEq6-0$?WQ^Vs|z$*Z1fXS=< z?D(zrGuco6u^+pb$WGB&m>7YHruDp&9}S7dL(pAJIlnpNSv~5RM?(z)rTgoH{}SjC z`(i|<-h406fsT-E>}Fuc$$+VmaeE@)7KtFTZOuP@n&E{1h4>r%vps zg52T*pejTpwZL{U-c4?tNTPVgezJ|XFoIvFDw8_Wj>-qmuv(&V8W%w!6c!>Vty9;k z53L%se`7^T>uKdN$XvQe)8Q+$s<4_veqh+rbedjkX=B=yHbe2zj2kE7YR#~~LkT*{ zZ5~6MX3~l@BVZiwnk2~Y+R99wM5X% z)N0yFU1h6>sGerk%po57!@}k{m-{u{;uX!H6@__PbcJ9SJw#@DTZ_X|T*C6CxED*o zkQ!|plCh$Sqf!u0M#YxBL!(|XoQhAXaw1L^En8<;zIw%DQAuQ?9FK_cL`d2}TdKrN zf>~Lah$MosUUARbbWx24RdF&ZO`cUmIZT_9IGMs&imS{5og)TCm<5*C*`j%$h)Z%L zdM1Hql>aq}B{hZPig-R4lSASY%_oj21<#Z<*QSCmO@ySR8toNFf-lKasVPw@gv)wG zH6bRkbROK2<6${2tCAQ?B$A5Q5|ZrUU@E5eQisKuJSD4M@=1BaObd8D0W>JBVlt>k zl?gS$2=J~g3#_!U@Cu5(;&)zc%{IO5>#a+e1UPpn3zn&Lo$%R$eLlPSBX);4!ODOEA6N2I&6D3&}*1)hPTfZF+6^ zCiE7qCN*zcpAjW*npvYXuRoWPQc}L?ysS{(LV5F3X7aWTPUnkOlaKF$aXlj+-vQYe zm!>C}vW+%@xV2B=bs?MyEBKEvMz^tInxTh#mRk|IlJYre?!Y54;izR7Y6{K2IO^T5{Vc-3hfsGCIMj2;Bx>f`E;zI>kSYgJ@_pEc0a?b@ZZM1sYECh zlLl~(HsMNEI%x*!YZswq;vcUiyT%Qf!1(8=eK*f_bT3vcrtUf4rLXKZ_F7Wgm7 C5G37 zEiRGnBszZ>^@o}JNak`AH%g0GN z>KmVqvhQ>qZ@EBJ1#jdi5rlam;Se(P^Gyj+qxS!%GcLH!_MPiX2%>h7Txhy)wj>0i z8zf0wP~A6Ve~^AY{pPV{OaDg)KYRIe=e6CJcVF^e^{%*TS6#I$mbz6--Lj?br|j?L zpHJL5@x#>Jsg>6L)z+9>!I69v> zUt zgiwstk^pQr8_j|>u8PbJ&(KmOvE!DQQieL^?5mu7Qq6wJ86{rDZ(1ig6(|=Gd=RQz~OEf&URh1rOA3J-%!6T<%R{|P*o;+&mWDTw&(#o(Oop?Fp)ayvArLE?gpUDKsrVCN__T?gSC7L_pJVDH5e|ZD@LSDny6D2+xX_ zOT%5!5UP%{<7Hk#m4z}J0SDL1uM!g$b4wu9j*52ndc{G|Sgg46oaE0`7WuX%&-0eH z*-s_c&``MQh^# z!T%I`3dKD8O|R1^2qVlOyvEx0E~fBy`4ppHo%;U`-e@XgLrvt_a6^I&bg00X{L5g z&G^kE0@lH!-CAoA+7yqs1Zd)JG(Cq&8czwmp#G#yvVT4q=T1 zeWa7U9~=O_7Od6>iJSbf zpeK450dtS5j)%jOv>o@u2r@!F0!6U1Q$vnjpCaW!;E(Qcs(&gh&rXK+({Eu*;EiM{ QcZYPMUf56eCJ?yxzs@9^v;Y7A 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