Solved 2015/09 P1+P2 and added TSP (short/long) function and dijkstras to helper file

This commit is contained in:
FrederikBaerentsen 2024-12-07 22:47:33 +01:00
parent 80a181656e
commit 1bf03abc75
5 changed files with 274 additions and 4 deletions

58
2015/09/9.md Normal file
View File

@ -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).

74
2015/09/solution.py Normal file
View File

@ -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))

View File

@ -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)
print(result)
print("--- %s seconds ---" % (time.time() - start_time))

Binary file not shown.

138
fred.py
View File

@ -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