diff --git a/graphs/tests/test_traveling_salesman_problem.py b/graphs/tests/test_traveling_salesman_problem.py new file mode 100644 index 000000000000..ed399a0dcb08 --- /dev/null +++ b/graphs/tests/test_traveling_salesman_problem.py @@ -0,0 +1,56 @@ +from graphs.traveling_salesman_problem import tsp_brute_force, tsp_dp, tsp_greedy + + +def sample_graph_1() -> list[list[int]]: + return [ + [0, 29, 20], + [29, 0, 15], + [20, 15, 0], + ] + + +def sample_graph_2() -> list[list[int]]: + return [ + [0, 10, 15, 20], + [10, 0, 35, 25], + [15, 35, 0, 30], + [20, 25, 30, 0], + ] + + +def test_brute_force() -> None: + graph = sample_graph_1() + assert tsp_brute_force(graph) == 64 + + +def test_dp() -> None: + graph = sample_graph_1() + assert tsp_dp(graph) == 64 + + +def test_greedy() -> None: + graph = sample_graph_1() + # The greedy algorithm does not guarantee an optimal solution; + # it is necessary to verify that its output is an integer greater than 0. + # An approximate solution cannot be represented by '==', + # and can only ensure that the result is reasonable. + result = tsp_greedy(graph) + assert isinstance(result, int) + assert result >= 64 + + +def test_dp_larger_graph() -> None: + graph = sample_graph_2() + assert tsp_dp(graph) == 80 + + +def test_brute_force_larger_graph() -> None: + graph = sample_graph_2() + assert tsp_brute_force(graph) == 80 + + +def test_greedy_larger_graph() -> None: + graph = sample_graph_2() + result = tsp_greedy(graph) + assert isinstance(result, int) + assert result >= 80 diff --git a/graphs/traveling_salesman_problem.py b/graphs/traveling_salesman_problem.py new file mode 100644 index 000000000000..33ec3e59aec4 --- /dev/null +++ b/graphs/traveling_salesman_problem.py @@ -0,0 +1,164 @@ +from itertools import permutations + + +def tsp_brute_force(graph: list[list[int]]) -> int: + """ + Solves TSP using brute-force permutations. + + Args: + graph: 2D list representing distances between cities. + + Returns: + The minimal total travel distance visiting all cities exactly once, + and then returning to the start. + + Example: + >>> tsp_brute_force([[0, 29, 20], [29, 0, 15], [20, 15, 0]]) + 64 + """ + n = len(graph) + # Apart from other cities aside from City 0, City 0 serves as the starting point. + nodes = list(range(1, n)) + min_path = float("inf") + + # Enumerate all the permutations from city 1 to city n-1. + for perm in permutations(nodes): + # Construct a complete path: + # Starting from point 0, visit in the order of arrangement, + # and then return to point 0. + path = [0, *perm, 0] + + # Calculate the total distance of the path. + # Update the shortest path. + total_cost = sum(graph[path[i]][path[i + 1]] for i in range(n)) + min_path = min(min_path, total_cost) + + return int(min_path) + + +def tsp_dp(graph: list[list[int]]) -> int: + """ + Solves the Traveling Salesman Problem using Held-Karp dynamic programming. + + Args: + graph: A 2D list representing distances between cities (n x n matrix). + + Returns: + The minimum cost to visit all cities exactly once and return to the origin. + + Example: + >>> tsp_dp([[0, 29, 20], [29, 0, 15], [20, 15, 0]]) + 64 + """ + n = len(graph) + # Create a dynamic programming table of size (2^n) x n. + # Noting: 1 << n = 2^n + # dp[mask][i] represents the shortest path starting from city 0, + # passing through the cities in the mask, and ultimately ending at city i. + dp = [[float("inf")] * n for _ in range(1 << n)] + # Initial state: only city 0 is visited, and the path length is 0. + dp[1][0] = 0 + + for mask in range(1 << n): + # The mask indicates which cities have been visited. + for u in range(n): + if not (mask & (1 << u)): + # If the city u is not included in the mask, skip it. + continue + + for v in range(n): + # City v has not been accessed and is different from city u. + if mask & (1 << v) or u == v: + continue + + # New State: Transition to city v + # State Transition: From city u to city v, updating the shortest path. + next_mask = mask | (1 << v) + dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + graph[u][v]) + + # After completing visits to all cities, + # return to city 0 and obtain the minimum value. + return int(min(dp[(1 << n) - 1][i] + graph[i][0] for i in range(1, n))) + + +def tsp_greedy(graph: list[list[int]]) -> int: + """ + Solves TSP approximately using the nearest neighbor heuristic. + Warming: This algorithm is not guaranteed to find the optimal solution! + But it is fast and applicable to any input size. + + Args: + graph: 2D list representing distances between cities. + + Returns: + The total distance of the approximated TSP route. + + Example: + >>> tsp_greedy([[0, 29, 20], [29, 0, 15], [20, 15, 0]]) + 64 + """ + n = len(graph) + visited = [False] * n # Mark whether each city has been visited. + path = [0] + total_cost = 0 + visited[0] = True # Start from city 0. + current = 0 # Current city. + + for _ in range(n - 1): + # Find the nearest city to the current location that has not been visited. + next_city = min( + ( + (city, cost) + for city, cost in enumerate(graph[current]) + if not visited[city] and city != current + ), + key=lambda cost: cost[1], + default=(None, float("inf")), + )[0] + + # If no such city exists, break the loop. + if next_city is None: + break + + # Update the total cost and the current city. + # Mark the city as visited. + # Append the city to the path. + total_cost += graph[current][next_city] + visited[next_city] = True + current = next_city + path.append(current) + + # Back to start + total_cost += graph[current][0] + path.append(0) + + return int(total_cost) + + +def test_tsp_example() -> None: + graph = [[0, 29, 20], [29, 0, 15], [20, 15, 0]] + + result = tsp_brute_force(graph) + if result != 64: + raise Exception("tsp_brute_force Incorrect result") + else: + print("Test passed") + + result = tsp_dp(graph) + if result != 64: + raise Exception("tsp_dp Incorrect result") + else: + print("Test passed") + + result = tsp_greedy(graph) + if result != 64: + if result < 0: + raise Exception("tsp_greedy Incorrect result") + else: + print("tsp_greedy gets an approximate result.") + else: + print("Test passed") + + +if __name__ == "__main__": + test_tsp_example() diff --git a/greedy_methods/fractional_knapsack.py b/greedy_methods/fractional_knapsack.py index d52b56f23569..f7455a9c9fce 100644 --- a/greedy_methods/fractional_knapsack.py +++ b/greedy_methods/fractional_knapsack.py @@ -39,9 +39,11 @@ def frac_knapsack(vl, wt, w, n): return ( 0 if k == 0 - else sum(vl[:k]) + (w - acc[k - 1]) * (vl[k]) / (wt[k]) - if k != n - else sum(vl[:k]) + else ( + sum(vl[:k]) + (w - acc[k - 1]) * (vl[k]) / (wt[k]) + if k != n + else sum(vl[:k]) + ) ) diff --git a/machine_learning/frequent_pattern_growth.py b/machine_learning/frequent_pattern_growth.py index fae2df16efb1..9f1817df2fb3 100644 --- a/machine_learning/frequent_pattern_growth.py +++ b/machine_learning/frequent_pattern_growth.py @@ -240,7 +240,7 @@ def ascend_tree(leaf_node: TreeNode, prefix_path: list[str]) -> None: ascend_tree(leaf_node.parent, prefix_path) -def find_prefix_path(base_pat: frozenset, tree_node: TreeNode | None) -> dict: # noqa: ARG001 +def find_prefix_path(_base_pat: frozenset, tree_node: TreeNode | None) -> dict: """ Find the conditional pattern base for a given base pattern. diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index a5940a38e836..230eb95006fa 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -204,9 +204,11 @@ def cofactors(self) -> Matrix: return Matrix( [ [ - self.minors().rows[row][column] - if (row + column) % 2 == 0 - else self.minors().rows[row][column] * -1 + ( + self.minors().rows[row][column] + if (row + column) % 2 == 0 + else self.minors().rows[row][column] * -1 + ) for column in range(self.minors().num_columns) ] for row in range(self.minors().num_rows) diff --git a/quantum/quantum_teleportation.py.DISABLED.txt b/quantum/quantum_teleportation.py.DISABLED.txt index 5da79ed20183..f57be19685ad 100644 --- a/quantum/quantum_teleportation.py.DISABLED.txt +++ b/quantum/quantum_teleportation.py.DISABLED.txt @@ -12,7 +12,8 @@ https://qiskit.org/textbook/ch-algorithms/teleportation.html import numpy as np import qiskit -from qiskit import Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute +from qiskit import (Aer, ClassicalRegister, QuantumCircuit, QuantumRegister, + execute) def quantum_teleportation( diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py index 8ce0619fd573..51133f7a2a64 100644 --- a/sorts/bead_sort.py +++ b/sorts/bead_sort.py @@ -3,6 +3,8 @@ https://en.wikipedia.org/wiki/Bead_sort """ +from itertools import pairwise + def bead_sort(sequence: list) -> list: """ @@ -31,7 +33,7 @@ def bead_sort(sequence: list) -> list: if any(not isinstance(x, int) or x < 0 for x in sequence): raise TypeError("Sequence must be list of non-negative integers") for _ in range(len(sequence)): - for i, (rod_upper, rod_lower) in enumerate(zip(sequence, sequence[1:])): # noqa: RUF007 + for i, (rod_upper, rod_lower) in enumerate(pairwise(sequence)): if rod_upper > rod_lower: sequence[i] -= rod_upper - rod_lower sequence[i + 1] += rod_upper - rod_lower diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 87eb5189e16a..70cd08d6faf4 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -140,7 +140,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]: elif op[0] == "R": string[i] = op[2] - file.write("%-16s" % ("Replace %c" % op[1] + " with " + str(op[2]))) # noqa: UP031 + file.write(f"{'Replace ' + op[1] + ' with ' + str(op[2]):<16}") file.write("\t\t" + "".join(string)) file.write("\r\n")