From 1bf03abc75adb326ecf601028e5bab5db4fa352a Mon Sep 17 00:00:00 2001 From: FrederikBaerentsen Date: Sat, 7 Dec 2024 22:47:33 +0100 Subject: [PATCH] Solved 2015/09 P1+P2 and added TSP (short/long) function and dijkstras to helper file --- 2015/09/9.md | 58 +++++++++++++ 2015/09/solution.py | 74 +++++++++++++++++ 2024/06/solution.py | 8 +- __pycache__/fred.cpython-311.pyc | Bin 17805 -> 23367 bytes fred.py | 138 ++++++++++++++++++++++++++++++- 5 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 2015/09/9.md create mode 100644 2015/09/solution.py diff --git a/2015/09/9.md b/2015/09/9.md new file mode 100644 index 0000000..cad67be --- /dev/null +++ b/2015/09/9.md @@ -0,0 +1,58 @@ +## \-\-- Day 9: All in a Single Night \-\-- + +Every year, Santa manages to deliver all of his presents in a single +night. + +This year, however, he has some [new +locations]{title="Bonus points if you recognize all of the locations."} +to visit; his elves have provided him the distances between every pair +of locations. He can start and end at any two (different) locations he +wants, but he must visit each location exactly once. What is the +*shortest distance* he can travel to achieve this? + +For example, given the following distances: + + London to Dublin = 464 + London to Belfast = 518 + Dublin to Belfast = 141 + +The possible routes are therefore: + + Dublin -> London -> Belfast = 982 + London -> Dublin -> Belfast = 605 + London -> Belfast -> Dublin = 659 + Dublin -> Belfast -> London = 659 + Belfast -> Dublin -> London = 605 + Belfast -> London -> Dublin = 982 + +The shortest of these is `London -> Dublin -> Belfast = 605`, and so the +answer is `605` in this example. + +What is the distance of the shortest route? + +Your puzzle answer was `117`. + +## \-\-- Part Two \-\-- {#part2} + +The next year, just to show off, Santa decides to take the route with +the *longest distance* instead. + +He can still start and end at any two (different) locations he wants, +and he still must visit each location exactly once. + +For example, given the distances above, the longest route would be `982` +via (for example) `Dublin -> London -> Belfast`. + +What is the distance of the longest route? + +Your puzzle answer was `909`. + +Both parts of this puzzle are complete! They provide two gold stars: +\*\* + +At this point, you should [return to your Advent calendar](/2015) and +try another puzzle. + +If you still want to see it, you can [get your puzzle +input](9/input). + diff --git a/2015/09/solution.py b/2015/09/solution.py new file mode 100644 index 0000000..cacbe7f --- /dev/null +++ b/2015/09/solution.py @@ -0,0 +1,74 @@ +#!/bin/python3 +import sys,time,re,json +from pprint import pprint + +sys.path.insert(0, '../../') +from fred import list2int,get_re,nprint,lprint,loadFile,dprint,TSP +start_time = time.time() + +input_f = 'input' + +part = 2 +######################################### +# # +# Part 1 # +# # +######################################### + +def constructGraph(input_f): + """ + The graph of cities should look something like this + + graph = { + "node": [("dist1", distance1), ("dist", distance2)], + } + """ + inst = [] + graph = {} + with open(input_f) as file: + for line in file: + inst = line.rstrip().split(' = ') + inst = inst[0].split(' to ') + [int(inst[1])] + if inst[0] not in graph: + graph[inst[0]] = [] + if inst[1] not in graph: + graph[inst[1]] = [] + graph[inst[0]].append((inst[1],inst[2])) + graph[inst[1]].append((inst[0],inst[2])) + + return graph + +if part == 1: + graph = constructGraph(input_f) + shortest = float('inf') + routes = [] + cities = set(graph.keys()) + for neighbors in graph.values(): + for neighbor, _ in neighbors: + cities.add(neighbor) + for c in cities: + print(TSP(graph, c)) + shortest = min(shortest,TSP(graph, c)[1]) + + print(shortest) + +######################################### +# # +# Part 2 # +# # +######################################### +if part == 2: + graph = constructGraph(input_f) + #print(graph) + longest = 0 + routes = [] + cities = set(graph.keys()) + for neighbors in graph.values(): + for neighbor, _ in neighbors: + cities.add(neighbor) + for c in cities: + longest = max(longest,TSP(graph, c,'longest')[1]) + + print(longest) + +print("--- %s seconds ---" % (time.time() - start_time)) \ No newline at end of file diff --git a/2024/06/solution.py b/2024/06/solution.py index 2351a11..7c69b06 100644 --- a/2024/06/solution.py +++ b/2024/06/solution.py @@ -1,8 +1,9 @@ #!/bin/python3 -import sys,re +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,grid_valid +start_time = time.time() input_f = 'input' @@ -130,6 +131,9 @@ if part == 2: result = 0 for idx,i in enumerate(steps): grid[i[0]][i[1]] = '#' + print(idx) result += isLoop(grid,start,'up') grid[i[0]][i[1]] = '.' - print(result) \ No newline at end of file + print(result) + +print("--- %s seconds ---" % (time.time() - start_time)) \ No newline at end of file diff --git a/__pycache__/fred.cpython-311.pyc b/__pycache__/fred.cpython-311.pyc index 0783001390a7e97b98b4f2ac8b23801dc2c3c916..1c60b63e2e60216d9c95e57b36ba169111afa788 100644 GIT binary patch delta 7229 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 delta 2148 zcmah~U1%It6rMXXyV;*)lWvmjCQUcLx=u|tO=6O!e^QgQrsCHASm;)b+uWPDW;?T< z-So$z%|lRVwIKIF5Cei>4T>lOg6Kovs?a`(w2Z#^riipJz62D{xwElpl)69P+*}?n2)?vE;?iaQCjpHO`7!&VRBTxf&i8As!K-S(8U`ALg~l(|H}PBgl*Ln5acwJ?`sx18R%$ zO^Ee;E02o?-Y9lOk=?Wu$Zg`y$cXaVVcSMnk2 zrkG=N4?fnveY8K5q2DYBo++L8LPaOf_ZNx+8d1=6KR0h0EG%CS?Mudy;MLAJ_89?{ z>0+T=ffSOqy0=2B=9Gy`2oMAbY7nfj7n;czi}_Qiu9c`Ef-nN!gXaeeb_FP^u$drA zP%B$wTYBqh*+8%fp-d_})CZIm!HRPL3LG9n`mmdiePm80NLZ;!;>t6M!uBFlHW%k6 z0UWRlc$g&81dkw;4Fpu_D>Tbz^Cjp-(lPg$#!vMzQs)IK3net26k0lD@B#e@WgTJd zWH`_HF%m1o5R&KRaN<<3k3_rN<-`|^HOoWE+4KNXEanAfoCUjL8J?cm4qKHJLe-W< zh|8an8TOWpw(QtLIxG`5`c)Pci$NEiVhAt#_J?N+HlJ81p%puk`kGv9Ib@Osxsdv^ z&kK|QL%rt1E;_v4hhS0hVJ|YS$mRA7>vM0k|DgB&bNPx6qsa6OaL%qjx*Fa+Cv0)9 z1bgN6t`4>$f9V=#eKOTO8mF+qHUi4H6@VT&*PVWJ0ykwU6b$}a5TmFeZ}aInN@^B) z?~~9CxtJc2ce>y6JxQ^I`(e+d-nI{!@C3m)iLLVlCZ(C~JpL{5_7kW?9H7+{0f}Nv z57Jth*Fr0@5o6ke4;9mDO@!w}Wn9=50dN>uU&{+=2Ltn4I%{Srol2XhtuW6JHeY2O zR*j#nL1Owv6h$npZ6y01}#n3QP{Fpr!j*)nn%U78;m(7PX06e#;@YpEo! zwI$i8yuE`eG&a_A;#s=mI70TmqR0_hWvS}iNg_N)@I1i_2pcQ7rd0AXXCrf)ygc@S zKLiHcft`WSgG;u3Jd-@~9|5P5l>>t(d&xI!UKJgc=xR6OG;x4$XaLm4j?3&gIk_}` z@Ls8+21+4Rd~Is2#*(f`Yjk2mm9EOXDzDZ9vTXtms->E>DoYebE4-mF{DDtGo;feh zm%RYw?K$C3fAeLhU|TUPm|GK_QHO5yr>it3CsizLAY`+a zkyYkpt z^ZA`al?(G~Tu@$tY+9*K6)N-9(C*U?Unq*ba0$1VL#=!X@vgxbyRC&6)%Ug*SybP< Yro3`uSw5BfPR33KBia;W2{oer1|LklvH$=8 diff --git a/fred.py b/fred.py index e544805..1a99389 100644 --- a/fred.py +++ b/fred.py @@ -1,5 +1,5 @@ -import re -import sys +import sys,re,heapq +from itertools import permutations def loadFile(input_f): """ @@ -139,6 +139,28 @@ def findDupes(input: list): raise TypeError("Input must be a list.") return [item for item in set(input) if input.count(item) > 1] +def dprint(graph: dict): + """ + Prints a dictionary in a formatted way, with each key-value pair on a new line. + + Args: + graph (dict): The dictionary to be printed. + + Returns: + None + + Raises: + TypeError: If the input is not a dictionary. + """ + if not isinstance(graph, dict): + raise TypeError("Input must be a dictionary.") + + try: + print("\n".join("{}\t\t{}".format(k, v) for k, v in graph.items())) + except Exception as e: + raise RuntimeError(f"An error occurred while printing the dictionary: {e}") + + def lprint(x: str, log: bool): """ Prints a string if logging is enabled. @@ -390,3 +412,115 @@ def get_value_in_direction(grid, position, direction=None, length=1, type: str = return ''.join(values) else: return values + +def dijkstra(graph, start, end): + """ + Dijkstra's Algorithm to find the shortest path between two nodes in a graph. + + Parameters: + - graph: A dictionary where each key is a node, and the value is a list of tuples. + Each tuple represents (neighbor, distance). + - start: The starting node. + - end: The destination node. + + Returns: + - path: A list of nodes that represent the shortest path from start to end. + - total_distance: The total distance of the shortest path. + """ + + priority_queue = [(0, start)] # Initially, we start with the starting node, with distance 0. + + distances = {node: float('inf') for node in graph} + distances[start] = 0 # The distance to the start node is 0 (we're already there). + + previous_nodes = {node: None for node in graph} + + while priority_queue: + + current_distance, current_node = heapq.heappop(priority_queue) # heapq.heappop() removes and returns the node with the smallest distance. + + # If we're at the destination node, we can stop. We've found the shortest path. + if current_node == end: + break + + for neighbor, weight in graph[current_node]: + distance = current_distance + weight + + if distance < distances[neighbor]: + distances[neighbor] = distance + previous_nodes[neighbor] = current_node + + heapq.heappush(priority_queue, (distance, neighbor)) + + path = [] # This will store the cities in the shortest path. + while end is not None: # Start from the destination and trace backward. + path.append(end) # Add the current node to the path. + end = previous_nodes[end] # Move to the previous node on the path. + + path.reverse() + + return path, distances[path[-1]] + +def TSP(graph, start, path_type='shortest'): + """ + Solves TSP (Traveling Salesperson Problem) using brute force by generating all possible paths. + Handles missing edges by skipping invalid paths. + + Parameters: + - graph: A dictionary where each key is a node, and the value is a list of tuples (neighbor, distance). + - start: The starting node. + - path_type: Either 'shortest' or 'longest' to find the shortest or longest path. + + Returns: + - shortest_path: The shortest path visiting all nodes exactly once and returning to the start. + - shortest_distance: The total distance of this path. + """ + # Validate path_type + if path_type not in ['shortest', 'longest']: + raise ValueError("path_type must be either 'shortest' or 'longest'.") + + # Extract all unique cities (keys + neighbors) + cities = set(graph.keys()) + for neighbors in graph.values(): + for neighbor, _ in neighbors: + cities.add(neighbor) + + # Ensure the start node is included + if start not in cities: + raise ValueError(f"Start location '{start}' not found in the graph.") + + # Remove the start city from the set for permutation + cities.remove(start) + + # Set the initial best_distance to the appropriate extreme (inf for shortest, -inf for longest) + best_distance = float('inf') if path_type == 'shortest' else -float('inf') + best_path = None + + # Generate all permutations of the remaining cities + for perm in permutations(cities): + # Create a complete path starting and ending at the start node + path = [start] + list(perm) + + # Calculate the total distance of this path + total_distance = 0 + valid_path = True + for i in range(len(path) - 1): + # Check if the edge exists in the graph + neighbors = dict(graph.get(path[i], [])) + if path[i + 1] in neighbors: + total_distance += neighbors[path[i + 1]] + else: + valid_path = False + break # Stop checking this path if it's invalid + + # If the path is valid, update the best_path based on the required path_type + if valid_path: + if (path_type == 'shortest' and total_distance < best_distance) or \ + (path_type == 'longest' and total_distance > best_distance): + best_distance = total_distance + best_path = path + + 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