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
|
#!/bin/python3
|
||||||
import sys,re
|
import sys,time,re
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
sys.path.insert(0, '../../')
|
sys.path.insert(0, '../../')
|
||||||
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid,get_value_in_direction,grid_valid
|
from fred import list2int,get_re,nprint,lprint,loadFile,toGrid,get_value_in_direction,grid_valid
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
input_f = 'input'
|
input_f = 'input'
|
||||||
|
|
||||||
@ -130,6 +131,9 @@ if part == 2:
|
|||||||
result = 0
|
result = 0
|
||||||
for idx,i in enumerate(steps):
|
for idx,i in enumerate(steps):
|
||||||
grid[i[0]][i[1]] = '#'
|
grid[i[0]][i[1]] = '#'
|
||||||
|
print(idx)
|
||||||
result += isLoop(grid,start,'up')
|
result += isLoop(grid,start,'up')
|
||||||
grid[i[0]][i[1]] = '.'
|
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,re,heapq
|
||||||
import sys
|
from itertools import permutations
|
||||||
|
|
||||||
def loadFile(input_f):
|
def loadFile(input_f):
|
||||||
"""
|
"""
|
||||||
@ -139,6 +139,28 @@ def findDupes(input: list):
|
|||||||
raise TypeError("Input must be a list.")
|
raise TypeError("Input must be a list.")
|
||||||
return [item for item in set(input) if input.count(item) > 1]
|
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):
|
def lprint(x: str, log: bool):
|
||||||
"""
|
"""
|
||||||
Prints a string if logging is enabled.
|
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)
|
return ''.join(values)
|
||||||
else:
|
else:
|
||||||
return values
|
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