[mypy] Add/fix type annotations for backtracking algorithms (#4055)

* Fix mypy errors for backtracking algorithms

* Fix CI failure
This commit is contained in:
Dhruv Manilawala 2020-12-24 18:16:21 +05:30 committed by GitHub
parent 0ccb213c11
commit f3ba9b6c50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 109 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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="")

View File

@ -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)

View File

@ -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.")