mirror of
https://hub.njuu.cf/TheAlgorithms/Python.git
synced 2023-10-11 13:06:12 +08:00
[mypy] Add/fix type annotations for backtracking algorithms (#4055)
* Fix mypy errors for backtracking algorithms * Fix CI failure
This commit is contained in:
parent
0ccb213c11
commit
f3ba9b6c50
@ -1,13 +1,12 @@
|
|||||||
|
"""
|
||||||
|
In this problem, we want to determine all possible subsequences
|
||||||
|
of the given sequence. We use backtracking to solve this problem.
|
||||||
|
|
||||||
|
Time complexity: O(2^n),
|
||||||
|
where n denotes the length of the given sequence.
|
||||||
|
"""
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
"""
|
|
||||||
In this problem, we want to determine all possible subsequences
|
|
||||||
of the given sequence. We use backtracking to solve this problem.
|
|
||||||
|
|
||||||
Time complexity: O(2^n),
|
|
||||||
where n denotes the length of the given sequence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def generate_all_subsequences(sequence: List[Any]) -> None:
|
def generate_all_subsequences(sequence: List[Any]) -> None:
|
||||||
create_state_space_tree(sequence, [], 0)
|
create_state_space_tree(sequence, [], 0)
|
||||||
@ -32,15 +31,10 @@ def create_state_space_tree(
|
|||||||
current_subsequence.pop()
|
current_subsequence.pop()
|
||||||
|
|
||||||
|
|
||||||
"""
|
if __name__ == "__main__":
|
||||||
remove the comment to take an input from the user
|
seq: List[Any] = [3, 1, 2, 4]
|
||||||
|
generate_all_subsequences(seq)
|
||||||
|
|
||||||
print("Enter the elements")
|
seq.clear()
|
||||||
sequence = list(map(int, input().split()))
|
seq.extend(["A", "B", "C"])
|
||||||
"""
|
generate_all_subsequences(seq)
|
||||||
|
|
||||||
sequence = [3, 1, 2, 4]
|
|
||||||
generate_all_subsequences(sequence)
|
|
||||||
|
|
||||||
sequence = ["A", "B", "C"]
|
|
||||||
generate_all_subsequences(sequence)
|
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
|
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def valid_coloring(
|
def valid_coloring(
|
||||||
neighbours: list[int], colored_vertices: list[int], color: int
|
neighbours: List[int], colored_vertices: List[int], color: int
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
For each neighbour check if coloring constraint is satisfied
|
For each neighbour check if coloring constraint is satisfied
|
||||||
@ -35,7 +35,7 @@ def valid_coloring(
|
|||||||
|
|
||||||
|
|
||||||
def util_color(
|
def util_color(
|
||||||
graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int
|
graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Pseudo-Code
|
Pseudo-Code
|
||||||
@ -86,7 +86,7 @@ def util_color(
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def color(graph: list[list[int]], max_colors: int) -> list[int]:
|
def color(graph: List[List[int]], max_colors: int) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Wrapper function to call subroutine called util_color
|
Wrapper function to call subroutine called util_color
|
||||||
which will either return True or False.
|
which will either return True or False.
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path
|
Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def valid_connection(
|
def valid_connection(
|
||||||
graph: list[list[int]], next_ver: int, curr_ind: int, path: list[int]
|
graph: List[List[int]], next_ver: int, curr_ind: int, path: List[int]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks whether it is possible to add next into path by validating 2 statements
|
Checks whether it is possible to add next into path by validating 2 statements
|
||||||
@ -47,7 +47,7 @@ def valid_connection(
|
|||||||
return not any(vertex == next_ver for vertex in path)
|
return not any(vertex == next_ver for vertex in path)
|
||||||
|
|
||||||
|
|
||||||
def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) -> bool:
|
def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Pseudo-Code
|
Pseudo-Code
|
||||||
Base Case:
|
Base Case:
|
||||||
@ -108,7 +108,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int)
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def hamilton_cycle(graph: list[list[int]], start_index: int = 0) -> list[int]:
|
def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]:
|
||||||
r"""
|
r"""
|
||||||
Wrapper function to call subroutine called util_hamilton_cycle,
|
Wrapper function to call subroutine called util_hamilton_cycle,
|
||||||
which will either return array of vertices indicating hamiltonian cycle
|
which will either return array of vertices indicating hamiltonian cycle
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM
|
# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM
|
||||||
|
|
||||||
from __future__ import annotations
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
|
||||||
def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]:
|
def get_valid_pos(position: Tuple[int, int], n: int) -> List[Tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Find all the valid positions a knight can move to from the current position.
|
Find all the valid positions a knight can move to from the current position.
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]:
|
|||||||
return permissible_positions
|
return permissible_positions
|
||||||
|
|
||||||
|
|
||||||
def is_complete(board: list[list[int]]) -> bool:
|
def is_complete(board: List[List[int]]) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the board (matrix) has been completely filled with non-zero values.
|
Check if the board (matrix) has been completely filled with non-zero values.
|
||||||
|
|
||||||
@ -46,7 +46,9 @@ def is_complete(board: list[list[int]]) -> bool:
|
|||||||
return not any(elem == 0 for row in board for elem in row)
|
return not any(elem == 0 for row in board for elem in row)
|
||||||
|
|
||||||
|
|
||||||
def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) -> bool:
|
def open_knight_tour_helper(
|
||||||
|
board: List[List[int]], pos: Tuple[int, int], curr: int
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Helper function to solve knight tour problem.
|
Helper function to solve knight tour problem.
|
||||||
"""
|
"""
|
||||||
@ -66,7 +68,7 @@ def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int)
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def open_knight_tour(n: int) -> list[list[int]]:
|
def open_knight_tour(n: int) -> List[List[int]]:
|
||||||
"""
|
"""
|
||||||
Find the solution for the knight tour problem for a board of size n. Raises
|
Find the solution for the knight tour problem for a board of size n. Raises
|
||||||
ValueError if the tour cannot be performed for the given size.
|
ValueError if the tour cannot be performed for the given size.
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import math
|
|
||||||
|
|
||||||
""" Minimax helps to achieve maximum score in a game by checking all possible moves
|
|
||||||
depth is current depth in game tree.
|
|
||||||
nodeIndex is index of current node in scores[].
|
|
||||||
if move is of maximizer return true else false
|
|
||||||
leaves of game tree is stored in scores[]
|
|
||||||
height is maximum height of Game tree
|
|
||||||
"""
|
"""
|
||||||
|
Minimax helps to achieve maximum score in a game by checking all possible moves
|
||||||
|
depth is current depth in game tree.
|
||||||
|
|
||||||
|
nodeIndex is index of current node in scores[].
|
||||||
|
if move is of maximizer return true else false
|
||||||
|
leaves of game tree is stored in scores[]
|
||||||
|
height is maximum height of Game tree
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def minimax(
|
def minimax(
|
||||||
depth: int, node_index: int, is_max: bool, scores: list[int], height: float
|
depth: int, node_index: int, is_max: bool, scores: List[int], height: float
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
>>> import math
|
>>> import math
|
||||||
@ -32,10 +32,6 @@ def minimax(
|
|||||||
>>> height = math.log(len(scores), 2)
|
>>> height = math.log(len(scores), 2)
|
||||||
>>> minimax(0, 0, True, scores, height)
|
>>> minimax(0, 0, True, scores, height)
|
||||||
12
|
12
|
||||||
>>> minimax('1', 2, True, [], 2 )
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if depth < 0:
|
if depth < 0:
|
||||||
@ -59,7 +55,7 @@ def minimax(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
scores = [90, 23, 6, 33, 21, 65, 123, 34423]
|
scores = [90, 23, 6, 33, 21, 65, 123, 34423]
|
||||||
height = math.log(len(scores), 2)
|
height = math.log(len(scores), 2)
|
||||||
print("Optimal value : ", end="")
|
print("Optimal value : ", end="")
|
||||||
|
@ -75,14 +75,14 @@ Applying this two formulas we can check if a queen in some position is being att
|
|||||||
for another one or vice versa.
|
for another one or vice versa.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def depth_first_search(
|
def depth_first_search(
|
||||||
possible_board: list[int],
|
possible_board: List[int],
|
||||||
diagonal_right_collisions: list[int],
|
diagonal_right_collisions: List[int],
|
||||||
diagonal_left_collisions: list[int],
|
diagonal_left_collisions: List[int],
|
||||||
boards: list[list[str]],
|
boards: List[List[str]],
|
||||||
n: int,
|
n: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -94,40 +94,33 @@ def depth_first_search(
|
|||||||
['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . ']
|
['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . ']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
""" Get next row in the current board (possible_board) to fill it with a queen """
|
# Get next row in the current board (possible_board) to fill it with a queen
|
||||||
row = len(possible_board)
|
row = len(possible_board)
|
||||||
|
|
||||||
"""
|
# If row is equal to the size of the board it means there are a queen in each row in
|
||||||
If row is equal to the size of the board it means there are a queen in each row in
|
# the current board (possible_board)
|
||||||
the current board (possible_board)
|
|
||||||
"""
|
|
||||||
if row == n:
|
if row == n:
|
||||||
"""
|
# We convert the variable possible_board that looks like this: [1, 3, 0, 2] to
|
||||||
We convert the variable possible_board that looks like this: [1, 3, 0, 2] to
|
# this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . ']
|
||||||
this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . ']
|
boards.append([". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board])
|
||||||
"""
|
|
||||||
possible_board = [". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board]
|
|
||||||
boards.append(possible_board)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
""" We iterate each column in the row to find all possible results in each row """
|
# We iterate each column in the row to find all possible results in each row
|
||||||
for col in range(n):
|
for col in range(n):
|
||||||
|
|
||||||
"""
|
# We apply that we learned previously. First we check that in the current board
|
||||||
We apply that we learned previously. First we check that in the current board
|
# (possible_board) there are not other same value because if there is it means
|
||||||
(possible_board) there are not other same value because if there is it means
|
# that there are a collision in vertical. Then we apply the two formulas we
|
||||||
that there are a collision in vertical. Then we apply the two formulas we
|
# learned before:
|
||||||
learned before:
|
#
|
||||||
|
# 45º: y - x = b or 45: row - col = b
|
||||||
45º: y - x = b or 45: row - col = b
|
# 135º: y + x = b or row + col = b.
|
||||||
135º: y + x = b or row + col = b.
|
#
|
||||||
|
# And we verify if the results of this two formulas not exist in their variables
|
||||||
And we verify if the results of this two formulas not exist in their variables
|
# respectively. (diagonal_right_collisions, diagonal_left_collisions)
|
||||||
respectively. (diagonal_right_collisions, diagonal_left_collisions)
|
#
|
||||||
|
# If any or these are True it means there is a collision so we continue to the
|
||||||
If any or these are True it means there is a collision so we continue to the
|
# next value in the for loop.
|
||||||
next value in the for loop.
|
|
||||||
"""
|
|
||||||
if (
|
if (
|
||||||
col in possible_board
|
col in possible_board
|
||||||
or row - col in diagonal_right_collisions
|
or row - col in diagonal_right_collisions
|
||||||
@ -135,7 +128,7 @@ def depth_first_search(
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
""" If it is False we call dfs function again and we update the inputs """
|
# If it is False we call dfs function again and we update the inputs
|
||||||
depth_first_search(
|
depth_first_search(
|
||||||
possible_board + [col],
|
possible_board + [col],
|
||||||
diagonal_right_collisions + [row - col],
|
diagonal_right_collisions + [row - col],
|
||||||
@ -146,10 +139,10 @@ def depth_first_search(
|
|||||||
|
|
||||||
|
|
||||||
def n_queens_solution(n: int) -> None:
|
def n_queens_solution(n: int) -> None:
|
||||||
boards = []
|
boards: List[List[str]] = []
|
||||||
depth_first_search([], [], [], boards, n)
|
depth_first_search([], [], [], boards, n)
|
||||||
|
|
||||||
""" Print all the boards """
|
# Print all the boards
|
||||||
for board in boards:
|
for board in boards:
|
||||||
for column in board:
|
for column in board:
|
||||||
print(column)
|
print(column)
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
from typing import List, Tuple, Union
|
"""
|
||||||
|
Given a partially filled 9×9 2D array, the objective is to fill a 9×9
|
||||||
|
square grid with digits numbered 1 to 9, so that every row, column, and
|
||||||
|
and each of the nine 3×3 sub-grids contains all of the digits.
|
||||||
|
|
||||||
|
This can be solved using Backtracking and is similar to n-queens.
|
||||||
|
We check to see if a cell is safe or not and recursively call the
|
||||||
|
function on the next column to see if it returns True. if yes, we
|
||||||
|
have solved the puzzle. else, we backtrack and place another number
|
||||||
|
in that cell and repeat this process.
|
||||||
|
"""
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
Matrix = List[List[int]]
|
Matrix = List[List[int]]
|
||||||
|
|
||||||
"""
|
|
||||||
Given a partially filled 9×9 2D array, the objective is to fill a 9×9
|
|
||||||
square grid with digits numbered 1 to 9, so that every row, column, and
|
|
||||||
and each of the nine 3×3 sub-grids contains all of the digits.
|
|
||||||
|
|
||||||
This can be solved using Backtracking and is similar to n-queens.
|
|
||||||
We check to see if a cell is safe or not and recursively call the
|
|
||||||
function on the next column to see if it returns True. if yes, we
|
|
||||||
have solved the puzzle. else, we backtrack and place another number
|
|
||||||
in that cell and repeat this process.
|
|
||||||
"""
|
|
||||||
# assigning initial values to the grid
|
# assigning initial values to the grid
|
||||||
initial_grid = [
|
initial_grid: Matrix = [
|
||||||
[3, 0, 6, 5, 0, 8, 4, 0, 0],
|
[3, 0, 6, 5, 0, 8, 4, 0, 0],
|
||||||
[5, 2, 0, 0, 0, 0, 0, 0, 0],
|
[5, 2, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[0, 8, 7, 0, 0, 0, 0, 3, 1],
|
[0, 8, 7, 0, 0, 0, 0, 3, 1],
|
||||||
@ -27,7 +27,7 @@ initial_grid = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# a grid with no solution
|
# a grid with no solution
|
||||||
no_solution = [
|
no_solution: Matrix = [
|
||||||
[5, 0, 6, 5, 0, 8, 4, 0, 3],
|
[5, 0, 6, 5, 0, 8, 4, 0, 3],
|
||||||
[5, 2, 0, 0, 0, 0, 0, 0, 2],
|
[5, 2, 0, 0, 0, 0, 0, 0, 2],
|
||||||
[1, 8, 7, 0, 0, 0, 0, 3, 1],
|
[1, 8, 7, 0, 0, 0, 0, 3, 1],
|
||||||
@ -80,7 +80,7 @@ def is_completed(grid: Matrix) -> bool:
|
|||||||
return all(all(cell != 0 for cell in row) for row in grid)
|
return all(all(cell != 0 for cell in row) for row in grid)
|
||||||
|
|
||||||
|
|
||||||
def find_empty_location(grid: Matrix) -> Tuple[int, int]:
|
def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
This function finds an empty location so that we can assign a number
|
This function finds an empty location so that we can assign a number
|
||||||
for that particular row and column.
|
for that particular row and column.
|
||||||
@ -89,9 +89,10 @@ def find_empty_location(grid: Matrix) -> Tuple[int, int]:
|
|||||||
for j in range(9):
|
for j in range(9):
|
||||||
if grid[i][j] == 0:
|
if grid[i][j] == 0:
|
||||||
return i, j
|
return i, j
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def sudoku(grid: Matrix) -> Union[Matrix, bool]:
|
def sudoku(grid: Matrix) -> Optional[Matrix]:
|
||||||
"""
|
"""
|
||||||
Takes a partially filled-in grid and attempts to assign values to
|
Takes a partially filled-in grid and attempts to assign values to
|
||||||
all unassigned locations in such a way to meet the requirements
|
all unassigned locations in such a way to meet the requirements
|
||||||
@ -107,25 +108,30 @@ def sudoku(grid: Matrix) -> Union[Matrix, bool]:
|
|||||||
[1, 3, 8, 9, 4, 7, 2, 5, 6],
|
[1, 3, 8, 9, 4, 7, 2, 5, 6],
|
||||||
[6, 9, 2, 3, 5, 1, 8, 7, 4],
|
[6, 9, 2, 3, 5, 1, 8, 7, 4],
|
||||||
[7, 4, 5, 2, 8, 6, 3, 1, 9]]
|
[7, 4, 5, 2, 8, 6, 3, 1, 9]]
|
||||||
>>> sudoku(no_solution)
|
>>> sudoku(no_solution) is None
|
||||||
False
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if is_completed(grid):
|
if is_completed(grid):
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
row, column = find_empty_location(grid)
|
location = find_empty_location(grid)
|
||||||
|
if location is not None:
|
||||||
|
row, column = location
|
||||||
|
else:
|
||||||
|
# If the location is ``None``, then the grid is solved.
|
||||||
|
return grid
|
||||||
|
|
||||||
for digit in range(1, 10):
|
for digit in range(1, 10):
|
||||||
if is_safe(grid, row, column, digit):
|
if is_safe(grid, row, column, digit):
|
||||||
grid[row][column] = digit
|
grid[row][column] = digit
|
||||||
|
|
||||||
if sudoku(grid):
|
if sudoku(grid) is not None:
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
grid[row][column] = 0
|
grid[row][column] = 0
|
||||||
|
|
||||||
return False
|
return None
|
||||||
|
|
||||||
|
|
||||||
def print_solution(grid: Matrix) -> None:
|
def print_solution(grid: Matrix) -> None:
|
||||||
@ -141,11 +147,12 @@ def print_solution(grid: Matrix) -> None:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# make a copy of grid so that you can compare with the unmodified grid
|
# make a copy of grid so that you can compare with the unmodified grid
|
||||||
for grid in (initial_grid, no_solution):
|
for example_grid in (initial_grid, no_solution):
|
||||||
grid = list(map(list, grid))
|
print("\nExample grid:\n" + "=" * 20)
|
||||||
solution = sudoku(grid)
|
print_solution(example_grid)
|
||||||
if solution:
|
print("\nExample grid solution:")
|
||||||
print("grid after solving:")
|
solution = sudoku(example_grid)
|
||||||
|
if solution is not None:
|
||||||
print_solution(solution)
|
print_solution(solution)
|
||||||
else:
|
else:
|
||||||
print("Cannot find a solution.")
|
print("Cannot find a solution.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user