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 0783001..1c60b63 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 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