Solved 2015/09 P1+P2 and added TSP (short/long) function and dijkstras to helper file
This commit is contained in:
parent
80a181656e
commit
1bf03abc75
58
2015/09/9.md
Normal file
58
2015/09/9.md
Normal 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
74
2015/09/solution.py
Normal 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))
|
@ -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
138
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
|
Loading…
Reference in New Issue
Block a user