From 3ceccfb59cec84f5871ad5efd31a3e43d42867b7 Mon Sep 17 00:00:00 2001
From: VarshiniShreeV <varshinishreevelumani@gmail.com>
Date: Sun, 27 Oct 2024 00:21:12 +0530
Subject: [PATCH 1/4] Fixed

---
 sorts/topological_sort.py | 27 ++++-----------------------
 1 file changed, 4 insertions(+), 23 deletions(-)

diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py
index efce8165fcac..7613d07b2d6c 100644
--- a/sorts/topological_sort.py
+++ b/sorts/topological_sort.py
@@ -1,10 +1,3 @@
-"""Topological Sort."""
-
-#     a
-#    / \
-#   b  c
-#  / \
-# d  e
 edges: dict[str, list[str]] = {
     "a": ["c", "b"],
     "b": ["d", "e"],
@@ -14,28 +7,16 @@
 }
 vertices: list[str] = ["a", "b", "c", "d", "e"]
 
-
 def topological_sort(start: str, visited: list[str], sort: list[str]) -> list[str]:
-    """Perform topological sort on a directed acyclic graph."""
+    visited.append(start)
     current = start
-    # add current to visited
-    visited.append(current)
-    neighbors = edges[current]
-    for neighbor in neighbors:
-        # if neighbor not in visited, visit
+    for neighbor in edges[start]:
         if neighbor not in visited:
-            sort = topological_sort(neighbor, visited, sort)
-    # if all neighbors visited add current to sort
+            topological_sort(neighbor, visited, sort)
     sort.append(current)
-    # if all vertices haven't been visited select a new one to visit
-    if len(visited) != len(vertices):
-        for vertice in vertices:
-            if vertice not in visited:
-                sort = topological_sort(vertice, visited, sort)
-    # return sort
     return sort
 
-
 if __name__ == "__main__":
     sort = topological_sort("a", [], [])
+    sort.reverse() #Top down approach
     print(sort)

From e321b1e444c55c6059689dcfe6b17127b916c4ff Mon Sep 17 00:00:00 2001
From: VarshiniShreeV <varshinishreevelumani@gmail.com>
Date: Sun, 27 Oct 2024 12:46:59 +0530
Subject: [PATCH 2/4] Added TSP

---
 travelling_salesman_problem.py | 226 +++++++++++++++++++++++++++++++++
 1 file changed, 226 insertions(+)
 create mode 100644 travelling_salesman_problem.py

diff --git a/travelling_salesman_problem.py b/travelling_salesman_problem.py
new file mode 100644
index 000000000000..70f6cf637d70
--- /dev/null
+++ b/travelling_salesman_problem.py
@@ -0,0 +1,226 @@
+""" Travelling Salesman Problem (TSP) """
+
+import itertools
+import math
+
+class InvalidGraphError(ValueError):
+    """Custom error for invalid graph inputs."""
+
+def euclidean_distance(point1: list[float], point2: list[float]) -> float:
+    """
+    Calculate the Euclidean distance between two points in 2D space.
+
+    :param point1: Coordinates of the first point [x, y]
+    :param point2: Coordinates of the second point [x, y]
+    :return: The Euclidean distance between the two points
+
+    >>> euclidean_distance([0, 0], [3, 4])
+    5.0
+    >>> euclidean_distance([1, 1], [1, 1])
+    0.0
+    >>> euclidean_distance([1, 1], ['a', 1])
+    Traceback (most recent call last):
+    ...
+    ValueError: Invalid input: Points must be numerical coordinates
+    """
+    try:
+        return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2)
+    except TypeError:
+        raise ValueError("Invalid input: Points must be numerical coordinates")
+
+def validate_graph(graph_points: dict[str, list[float]]) -> None:
+    """
+    Validate the input graph to ensure it has valid nodes and coordinates.
+
+    :param graph_points: A dictionary where the keys are node names,
+                         and values are 2D coordinates as [x, y]
+    :raises InvalidGraphError: If the graph points are not valid
+
+    >>> validate_graph({"A": [10, 20], "B": [30, 21], "C": [15, 35]})  # Valid graph
+    >>> validate_graph({"A": [10, 20], "B": [30, "invalid"], "C": [15, 35]})
+    Traceback (most recent call last):
+    ...
+    InvalidGraphError: Each node must have a valid 2D coordinate [x, y]
+    
+    >>> validate_graph([10, 20])  # Invalid input type
+    Traceback (most recent call last):
+    ...
+    InvalidGraphError: Graph must be a dictionary with node names and coordinates
+    
+    >>> validate_graph({"A": [10, 20], "B": [30, 21], "C": [15]})  # Missing coordinate
+    Traceback (most recent call last):
+    ...
+    InvalidGraphError: Each node must have a valid 2D coordinate [x, y]
+    """
+    if not isinstance(graph_points, dict):
+        raise InvalidGraphError(
+            "Graph must be a dictionary with node names and coordinates"
+        )
+
+    for node, coordinates in graph_points.items():
+        if (
+            not isinstance(node, str)
+            or not isinstance(coordinates, list)
+            or len(coordinates) != 2
+            or not all(isinstance(c, (int, float)) for c in coordinates)
+        ):
+            raise InvalidGraphError("Each node must have a valid 2D coordinate [x, y]")
+
+# TSP in Brute Force Approach
+def travelling_salesman_brute_force(
+    graph_points: dict[str, list[float]],
+) -> tuple[list[str], float]:
+    """
+    Solve the Travelling Salesman Problem using brute force.
+
+    :param graph_points: A dictionary of nodes and their coordinates {node: [x, y]}
+    :return: The shortest path and its total distance
+
+    >>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]}
+    >>> travelling_salesman_brute_force(graph)
+    (['A', 'C', 'B', 'A'], 56.35465722402587)
+    """
+    validate_graph(graph_points)
+
+    nodes = list(graph_points.keys())  # Extracting the node names (keys)
+
+    # There shoukd be atleast 2 nodes for a valid TSP
+    if len(nodes) < 2:
+        raise InvalidGraphError("Graph must have at least two nodes")
+
+    min_path = []  # List that stores shortest path
+    min_distance = float("inf") # Initialize minimum distance to infinity
+
+    start_node = nodes[0]
+    other_nodes = nodes[1:]
+
+    # Iterating over all permutations of the other nodes
+    for perm in itertools.permutations(other_nodes):
+        path = [start_node, *perm, start_node]
+
+        # Calculating the total distance
+        total_distance = sum(
+            euclidean_distance(graph_points[path[i]], graph_points[path[i + 1]])
+            for i in range(len(path) - 1)
+        )
+
+        # Update minimum distance if shorter path found
+        if total_distance < min_distance:
+            min_distance = total_distance
+            min_path = path
+
+    return min_path, min_distance
+
+# TSP in Dynamic Programming approach
+def travelling_salesman_dynamic_programming(
+    graph_points: dict[str, list[float]],
+) -> tuple[list[str], float]:
+    """
+    Solve the Travelling Salesman Problem using dynamic programming.
+
+    :param graph_points: A dictionary of nodes and their coordinates {node: [x, y]}
+    :return: The shortest path and its total distance
+
+    >>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]}
+    >>> travelling_salesman_dynamic_programming(graph)
+    (['A', 'C', 'B', 'A'], 56.35465722402587)
+    """
+    validate_graph(graph_points)
+
+    n = len(graph_points)    # Extracting the node names (keys)
+
+    # There shoukd be atleast 2 nodes for a valid TSP
+    if n < 2:
+        raise InvalidGraphError("Graph must have at least two nodes")
+
+    nodes = list(graph_points.keys())    # Extracting the node names (keys)
+
+    # Initialize distance matrix with float values
+    dist = [[euclidean_distance(graph_points[nodes[i]], graph_points[nodes[j]]) for j in range(n)] for i in range(n)]
+
+    # Initialize a dynamic programming table with infinity 
+    dp = [[float("inf")] * n for _ in range(1 << n)]
+    dp[1][0] = 0     # Only visited node is the starting point at node 0
+
+    # Iterate through all masks of visited nodes
+    for mask in range(1 << n):
+        for u in range(n):
+            # If current node 'u' is visited
+            if mask & (1 << u):
+                # Traverse nodes 'v' such that u->v
+                for v in range(n):
+                    if mask & (1 << v) == 0:   # If v is not visited
+                        next_mask = mask | (1 << v)     # Upodate mask to include 'v'
+                        # Update dynamic programming table with minimum distance
+                        dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + dist[u][v])
+
+    final_mask = (1 << n) - 1
+    min_cost = float("inf")
+    end_node = -1    # Track the last node in the optimal path
+
+    for u in range(1, n):
+        if min_cost > dp[final_mask][u] + dist[u][0]:
+            min_cost = dp[final_mask][u] + dist[u][0]
+            end_node = u
+
+    path = []
+    mask = final_mask
+    while end_node != 0:
+        path.append(nodes[end_node])
+        for u in range(n):
+            # If current state corresponds to optimal state before visiting end node
+            if (
+                mask & (1 << u)
+                and dp[mask][end_node]
+                == dp[mask ^ (1 << end_node)][u] + dist[u][end_node]
+            ):
+                mask ^= 1 << end_node  # Update mask to remove end node
+                end_node = u    # Set the previous node as end node
+                break
+
+    path.append(nodes[0])  # Bottom-up Order
+    path.reverse()  # Top-Down Order
+    path.append(nodes[0])
+
+    return path, min_cost
+
+
+# Demo Graph
+#        C (15, 35)
+#        |
+#        |
+#        |
+# F (5, 15) --- A (10, 20)
+#        |         |
+#        |         |
+#        |         |
+#        |         |
+# E (25, 5) --- B (30, 21)
+#        |
+#        |
+#        |
+#       D (40, 10)
+#        |
+#        |
+#        |
+#       G (50, 25)
+
+
+if __name__ == "__main__":
+    demo_graph = {
+        "A": [10.0, 20.0],
+        "B": [30.0, 21.0],
+        "C": [15.0, 35.0],
+        "D": [40.0, 10.0],
+        "E": [25.0, 5.0],
+        "F": [5.0, 15.0],
+        "G": [50.0, 25.0],
+    }
+
+    # Brute force
+    brute_force_result = travelling_salesman_brute_force(demo_graph)
+    print(f"Brute force result: {brute_force_result}")
+
+    # Dynamic programming
+    dp_result = travelling_salesman_dynamic_programming(demo_graph)
+    print(f"Dynamic programming result: {dp_result}")

From 76db9e005b6bdb4652425fce9cc737cc13ba6d75 Mon Sep 17 00:00:00 2001
From: VarshiniShreeV <varshinishreevelumani@gmail.com>
Date: Sun, 27 Oct 2024 12:48:38 +0530
Subject: [PATCH 3/4] Fixes 12192

---
 sorts/topological_sort.py | 37 +++++++++++++++++++++++++++++++++----
 1 file changed, 33 insertions(+), 4 deletions(-)

diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py
index 7613d07b2d6c..351f185294a3 100644
--- a/sorts/topological_sort.py
+++ b/sorts/topological_sort.py
@@ -1,3 +1,11 @@
+"""Topological Sort on Directed Acyclic Graph(DAG)"""
+
+#     a
+#    / \
+#   b   c
+#  / \
+# d   e
+
 edges: dict[str, list[str]] = {
     "a": ["c", "b"],
     "b": ["d", "e"],
@@ -5,18 +13,39 @@
     "d": [],
     "e": [],
 }
+
 vertices: list[str] = ["a", "b", "c", "d", "e"]
 
+# Perform topological sort on a DAG starting from the specified node
 def topological_sort(start: str, visited: list[str], sort: list[str]) -> list[str]:
-    visited.append(start)
     current = start
-    for neighbor in edges[start]:
+    # Mark the current node as visited
+    visited.append(current)
+    # List of all neighbors of current node
+    neighbors = edges[current]
+
+    # Traverse all neighbors of the current node
+    for neighbor in neighbors:
+        # Recursively visit each unvisited neighbor
         if neighbor not in visited:
-            topological_sort(neighbor, visited, sort)
+            sort = topological_sort(neighbor, visited, sort)
+
+    # After visiting all neighbors, add the current node to the sorted list
     sort.append(current)
+
+    # If there are some nodes that were not visited (disconnected components)
+    if len(visited) != len(vertices):
+        for vertex in vertices:
+            if vertex not in visited:
+                sort = topological_sort(vertex, visited, sort)
+
+    # Return sorted list
     return sort
 
 if __name__ == "__main__":
+    # Topological Sorting from node "a" (Returns the order in bottom up approach)
     sort = topological_sort("a", [], [])
-    sort.reverse() #Top down approach
+
+    # Reversing the list to get the correct topological order (Top down approach)
+    sort.reverse()  
     print(sort)

From 613f482360dfb51322d8dae2c8bed5bf7ac8ca6b Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sun, 27 Oct 2024 07:23:56 +0000
Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 sorts/topological_sort.py      |  4 +++-
 travelling_salesman_problem.py | 43 ++++++++++++++++++++++------------
 2 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py
index 351f185294a3..90035b460225 100644
--- a/sorts/topological_sort.py
+++ b/sorts/topological_sort.py
@@ -16,6 +16,7 @@
 
 vertices: list[str] = ["a", "b", "c", "d", "e"]
 
+
 # Perform topological sort on a DAG starting from the specified node
 def topological_sort(start: str, visited: list[str], sort: list[str]) -> list[str]:
     current = start
@@ -42,10 +43,11 @@ def topological_sort(start: str, visited: list[str], sort: list[str]) -> list[st
     # Return sorted list
     return sort
 
+
 if __name__ == "__main__":
     # Topological Sorting from node "a" (Returns the order in bottom up approach)
     sort = topological_sort("a", [], [])
 
     # Reversing the list to get the correct topological order (Top down approach)
-    sort.reverse()  
+    sort.reverse()
     print(sort)
diff --git a/travelling_salesman_problem.py b/travelling_salesman_problem.py
index 70f6cf637d70..5c69420a0723 100644
--- a/travelling_salesman_problem.py
+++ b/travelling_salesman_problem.py
@@ -1,11 +1,13 @@
-""" Travelling Salesman Problem (TSP) """
+"""Travelling Salesman Problem (TSP)"""
 
 import itertools
 import math
 
+
 class InvalidGraphError(ValueError):
     """Custom error for invalid graph inputs."""
 
+
 def euclidean_distance(point1: list[float], point2: list[float]) -> float:
     """
     Calculate the Euclidean distance between two points in 2D space.
@@ -28,6 +30,7 @@ def euclidean_distance(point1: list[float], point2: list[float]) -> float:
     except TypeError:
         raise ValueError("Invalid input: Points must be numerical coordinates")
 
+
 def validate_graph(graph_points: dict[str, list[float]]) -> None:
     """
     Validate the input graph to ensure it has valid nodes and coordinates.
@@ -41,12 +44,12 @@ def validate_graph(graph_points: dict[str, list[float]]) -> None:
     Traceback (most recent call last):
     ...
     InvalidGraphError: Each node must have a valid 2D coordinate [x, y]
-    
+
     >>> validate_graph([10, 20])  # Invalid input type
     Traceback (most recent call last):
     ...
     InvalidGraphError: Graph must be a dictionary with node names and coordinates
-    
+
     >>> validate_graph({"A": [10, 20], "B": [30, 21], "C": [15]})  # Missing coordinate
     Traceback (most recent call last):
     ...
@@ -66,6 +69,7 @@ def validate_graph(graph_points: dict[str, list[float]]) -> None:
         ):
             raise InvalidGraphError("Each node must have a valid 2D coordinate [x, y]")
 
+
 # TSP in Brute Force Approach
 def travelling_salesman_brute_force(
     graph_points: dict[str, list[float]],
@@ -89,7 +93,7 @@ def travelling_salesman_brute_force(
         raise InvalidGraphError("Graph must have at least two nodes")
 
     min_path = []  # List that stores shortest path
-    min_distance = float("inf") # Initialize minimum distance to infinity
+    min_distance = float("inf")  # Initialize minimum distance to infinity
 
     start_node = nodes[0]
     other_nodes = nodes[1:]
@@ -111,6 +115,7 @@ def travelling_salesman_brute_force(
 
     return min_path, min_distance
 
+
 # TSP in Dynamic Programming approach
 def travelling_salesman_dynamic_programming(
     graph_points: dict[str, list[float]],
@@ -127,20 +132,26 @@ def travelling_salesman_dynamic_programming(
     """
     validate_graph(graph_points)
 
-    n = len(graph_points)    # Extracting the node names (keys)
+    n = len(graph_points)  # Extracting the node names (keys)
 
     # There shoukd be atleast 2 nodes for a valid TSP
     if n < 2:
         raise InvalidGraphError("Graph must have at least two nodes")
 
-    nodes = list(graph_points.keys())    # Extracting the node names (keys)
+    nodes = list(graph_points.keys())  # Extracting the node names (keys)
 
     # Initialize distance matrix with float values
-    dist = [[euclidean_distance(graph_points[nodes[i]], graph_points[nodes[j]]) for j in range(n)] for i in range(n)]
-
-    # Initialize a dynamic programming table with infinity 
+    dist = [
+        [
+            euclidean_distance(graph_points[nodes[i]], graph_points[nodes[j]])
+            for j in range(n)
+        ]
+        for i in range(n)
+    ]
+
+    # Initialize a dynamic programming table with infinity
     dp = [[float("inf")] * n for _ in range(1 << n)]
-    dp[1][0] = 0     # Only visited node is the starting point at node 0
+    dp[1][0] = 0  # Only visited node is the starting point at node 0
 
     # Iterate through all masks of visited nodes
     for mask in range(1 << n):
@@ -149,14 +160,16 @@ def travelling_salesman_dynamic_programming(
             if mask & (1 << u):
                 # Traverse nodes 'v' such that u->v
                 for v in range(n):
-                    if mask & (1 << v) == 0:   # If v is not visited
-                        next_mask = mask | (1 << v)     # Upodate mask to include 'v'
+                    if mask & (1 << v) == 0:  # If v is not visited
+                        next_mask = mask | (1 << v)  # Upodate mask to include 'v'
                         # Update dynamic programming table with minimum distance
-                        dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + dist[u][v])
+                        dp[next_mask][v] = min(
+                            dp[next_mask][v], dp[mask][u] + dist[u][v]
+                        )
 
     final_mask = (1 << n) - 1
     min_cost = float("inf")
-    end_node = -1    # Track the last node in the optimal path
+    end_node = -1  # Track the last node in the optimal path
 
     for u in range(1, n):
         if min_cost > dp[final_mask][u] + dist[u][0]:
@@ -175,7 +188,7 @@ def travelling_salesman_dynamic_programming(
                 == dp[mask ^ (1 << end_node)][u] + dist[u][end_node]
             ):
                 mask ^= 1 << end_node  # Update mask to remove end node
-                end_node = u    # Set the previous node as end node
+                end_node = u  # Set the previous node as end node
                 break
 
     path.append(nodes[0])  # Bottom-up Order