Skip to content

Commit 5fe84b5

Browse files
committed
Added travelling saleman problem
1 parent d6b11f7 commit 5fe84b5

File tree

3 files changed

+122
-81
lines changed

3 files changed

+122
-81
lines changed
+110-74
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,93 @@
1-
"""
2-
title : Travelling Sales man Problem
3-
references : https://en.wikipedia.org/wiki/Travelling_salesman_problem
4-
author : SunayBhoyar
5-
"""
6-
1+
#!/usr/bin/env python3
72
import itertools
83
import math
94

10-
demo_graph_points = {
11-
"A": [10, 20],
12-
"B": [30, 21],
13-
"C": [15, 35],
14-
"D": [40, 10],
15-
"E": [25, 5],
16-
"F": [5, 15],
17-
"G": [50, 25],
18-
}
5+
6+
class InvalidGraphError(ValueError):
7+
"""Custom error for invalid graph inputs."""
198

209

21-
# Euclidean distance - shortest distance between 2 points
22-
def distance(point1, point2):
10+
def euclidean_distance(point1: list[float], point2: list[float]) -> float:
2311
"""
24-
Calculate the Euclidean distance between two points.
25-
@input: point1, point2 (coordinates of two points as lists [x, y])
26-
@return: Euclidean distance between point1 and point2
27-
@example:
28-
>>> distance([0, 0], [3, 4])
12+
Calculate the Euclidean distance between two points in 2D space.
13+
14+
:param point1: Coordinates of the first point [x, y]
15+
:param point2: Coordinates of the second point [x, y]
16+
:return: The Euclidean distance between the two points
17+
18+
>>> euclidean_distance([0, 0], [3, 4])
2919
5.0
20+
>>> euclidean_distance([1, 1], [1, 1])
21+
0.0
22+
>>> euclidean_distance([1, 1], ['a', 1])
23+
Traceback (most recent call last):
24+
...
25+
ValueError: Invalid input: Points must be numerical coordinates
3026
"""
31-
return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2)
27+
try:
28+
return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2)
29+
except TypeError:
30+
raise ValueError("Invalid input: Points must be numerical coordinates")
3231

3332

34-
"""
35-
Brute force
36-
Time Complexity - O(n!×n)
37-
Space Complexity - O(n)
38-
"""
39-
33+
def validate_graph(graph_points: dict[str, list[float]]) -> None:
34+
"""
35+
Validate the input graph to ensure it has valid nodes and coordinates.
4036
41-
def travelling_sales_man_problem_brute_force(graph_points):
37+
:param graph_points: A dictionary where the keys are node names,
38+
and values are 2D coordinates as [x, y]
39+
:raises InvalidGraphError: If the graph points are not valid
40+
"""
41+
if not isinstance(graph_points, dict):
42+
raise InvalidGraphError(
43+
"Graph must be a dictionary with node names and coordinates"
44+
)
45+
46+
for node, coordinates in graph_points.items():
47+
if (
48+
not isinstance(node, str)
49+
or not isinstance(coordinates, list)
50+
or len(coordinates) != 2
51+
):
52+
raise InvalidGraphError("Each node must have a valid 2D coordinate [x, y]")
53+
54+
55+
def travelling_salesman_brute_force(
56+
graph_points: dict[str, list[float]],
57+
) -> tuple[list[str], float]:
4258
"""
43-
Solve the Travelling Salesman Problem using brute force (permutations).
44-
@input: graph_points (dictionary with node names as keys and coordinates as values)
45-
@return: shortest path, total distance (list of nodes representing the shortest path and the distance of that path)
46-
@example:
47-
>>> travelling_sales_man_problem_brute_force({'A': [0, 0], 'B': [0, 1], 'C': [1, 0]})
48-
(['A', 'B', 'C', 'A'], 3.414)
59+
Solve the Travelling Salesman Problem using brute force.
60+
61+
:param graph_points: A dictionary of nodes and their coordinates {node: [x, y]}
62+
:return: The shortest path and its total distance
63+
64+
>>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]}
65+
>>> travelling_salesman_brute_force(graph)
66+
(['A', 'B', 'C', 'A'], 65.52370249788875)
67+
>>> travelling_salesman_brute_force({})
68+
Traceback (most recent call last):
69+
...
70+
InvalidGraphError: Graph must have at least two nodes
4971
"""
72+
validate_graph(graph_points)
73+
5074
nodes = list(graph_points.keys())
75+
if len(nodes) < 2:
76+
raise InvalidGraphError("Graph must have at least two nodes")
5177

52-
min_path = None
78+
min_path = []
5379
min_distance = float("inf")
5480

55-
# Considering the first Node as the start position
5681
start_node = nodes[0]
5782
other_nodes = nodes[1:]
5883

5984
for perm in itertools.permutations(other_nodes):
60-
path = [start_node] + list(perm) + [start_node]
61-
total_distance = 0
85+
path = [start_node, *list(perm), start_node]
86+
total_distance = 0.0
6287
for i in range(len(path) - 1):
63-
total_distance += distance(graph_points[path[i]], graph_points[path[i + 1]])
88+
total_distance += euclidean_distance(
89+
graph_points[path[i]], graph_points[path[i + 1]]
90+
)
6491

6592
if total_distance < min_distance:
6693
min_distance = total_distance
@@ -69,36 +96,42 @@ def travelling_sales_man_problem_brute_force(graph_points):
6996
return min_path, min_distance
7097

7198

72-
"""
73-
dynamic_programming
74-
Time Complexity - O(n^2×2^n)
75-
Space Complexity - O(n×2^n)
76-
"""
77-
78-
79-
def travelling_sales_man_problem_dp(graph_points):
99+
def travelling_salesman_dynamic_programming(
100+
graph_points: dict[str, list[float]],
101+
) -> tuple[list[str], float]:
80102
"""
81103
Solve the Travelling Salesman Problem using dynamic programming.
82-
@input: graph_points (dictionary with node names as keys and coordinates as values)
83-
@return: shortest path, total distance (list of nodes representing the shortest path and the distance of that path)
84-
@example:
85-
>>> travelling_sales_man_problem_dp({'A': [0, 0], 'B': [0, 1], 'C': [1, 0]})
86-
(['A', 'B', 'C', 'A'], 3.414)
104+
105+
:param graph_points: A dictionary of nodes and their coordinates {node: [x, y]}
106+
:return: The shortest path and its total distance
107+
108+
>>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]}
109+
>>> travelling_salesman_dynamic_programming(graph)
110+
(['A', 'B', 'C', 'A'], 65.52370249788875)
111+
>>> travelling_salesman_dynamic_programming({})
112+
Traceback (most recent call last):
113+
...
114+
InvalidGraphError: Graph must have at least two nodes
87115
"""
116+
validate_graph(graph_points)
117+
88118
n = len(graph_points)
119+
if n < 2:
120+
raise InvalidGraphError("Graph must have at least two nodes")
121+
89122
nodes = list(graph_points.keys())
90123

91-
# Precompute distances between every pair of nodes
92-
dist = [[0] * n for _ in range(n)]
124+
# Initialize distance matrix with float values
125+
dist = [[0.0] * n for _ in range(n)]
93126
for i in range(n):
94127
for j in range(n):
95-
dist[i][j] = distance(graph_points[nodes[i]], graph_points[nodes[j]])
128+
dist[i][j] = euclidean_distance(
129+
graph_points[nodes[i]], graph_points[nodes[j]]
130+
)
96131

97-
# dp[mask][i] represents the minimum distance to visit all nodes in the 'mask' set, ending at node i
98132
dp = [[float("inf")] * n for _ in range(1 << n)]
99-
dp[1][0] = 0 # Start at node 0
133+
dp[1][0] = 0
100134

101-
# Iterate over all subsets of nodes (represented by mask)
102135
for mask in range(1 << n):
103136
for u in range(n):
104137
if mask & (1 << u):
@@ -109,18 +142,15 @@ def travelling_sales_man_problem_dp(graph_points):
109142
dp[next_mask][v], dp[mask][u] + dist[u][v]
110143
)
111144

112-
# Reconstruct the path and find the minimum distance to return to the start
113145
final_mask = (1 << n) - 1
114146
min_cost = float("inf")
115147
end_node = -1
116148

117-
# Find the minimum distance from any node back to the starting node
118149
for u in range(1, n):
119150
if min_cost > dp[final_mask][u] + dist[u][0]:
120151
min_cost = dp[final_mask][u] + dist[u][0]
121152
end_node = u
122153

123-
# Reconstruct the path using the dp table
124154
path = []
125155
mask = final_mask
126156
while end_node != 0:
@@ -142,14 +172,20 @@ def travelling_sales_man_problem_dp(graph_points):
142172

143173

144174
if __name__ == "__main__":
145-
print(f"Travelling salesman problem solved using Brute Force:")
146-
path, distance_travelled = travelling_sales_man_problem_brute_force(
147-
demo_graph_points
148-
)
149-
print(f"Shortest path: {path}")
150-
print(f"Total distance: {distance_travelled:.2f}")
151-
152-
print(f"\nTravelling salesman problem solved using Dynamic Programming:")
153-
path, distance_travelled = travelling_sales_man_problem_dp(demo_graph_points)
154-
print(f"Shortest path: {path}")
155-
print(f"Total distance: {distance_travelled:.2f}")
175+
demo_graph = {
176+
"A": [10.0, 20.0],
177+
"B": [30.0, 21.0],
178+
"C": [15.0, 35.0],
179+
"D": [40.0, 10.0],
180+
"E": [25.0, 5.0],
181+
"F": [5.0, 15.0],
182+
"G": [50.0, 25.0],
183+
}
184+
185+
# Example usage for brute force
186+
brute_force_result = travelling_salesman_brute_force(demo_graph)
187+
print(f"Brute force result: {brute_force_result}")
188+
189+
# Example usage for dynamic programming
190+
dp_result = travelling_salesman_dynamic_programming(demo_graph)
191+
print(f"Dynamic programming result: {dp_result}")

greedy_methods/fractional_knapsack.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#!/usr/bin/env python3
12
from bisect import bisect
23
from itertools import accumulate
34

@@ -39,9 +40,11 @@ def frac_knapsack(vl, wt, w, n):
3940
return (
4041
0
4142
if k == 0
42-
else sum(vl[:k]) + (w - acc[k - 1]) * (vl[k]) / (wt[k])
43-
if k != n
44-
else sum(vl[:k])
43+
else (
44+
sum(vl[:k]) + (w - acc[k - 1]) * (vl[k]) / (wt[k])
45+
if k != n
46+
else sum(vl[:k])
47+
)
4548
)
4649

4750

matrix/matrix_class.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
#!/usr/bin/env python3
12
# An OOP approach to representing and manipulating matrices
2-
33
from __future__ import annotations
44

55

@@ -204,9 +204,11 @@ def cofactors(self) -> Matrix:
204204
return Matrix(
205205
[
206206
[
207-
self.minors().rows[row][column]
208-
if (row + column) % 2 == 0
209-
else self.minors().rows[row][column] * -1
207+
(
208+
self.minors().rows[row][column]
209+
if (row + column) % 2 == 0
210+
else self.minors().rows[row][column] * -1
211+
)
210212
for column in range(self.minors().num_columns)
211213
]
212214
for row in range(self.minors().num_rows)

0 commit comments

Comments
 (0)