From 4c8450f894ff3ddf18cbf2e9f6faf08ab7dc0c17 Mon Sep 17 00:00:00 2001 From: dbring Date: Wed, 4 Oct 2023 18:37:39 -0700 Subject: [PATCH 01/19] Added algorithm to deeply clone a graph --- graphs/deep-clone-graph.py | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 graphs/deep-clone-graph.py diff --git a/graphs/deep-clone-graph.py b/graphs/deep-clone-graph.py new file mode 100644 index 000000000000..0ddf0cf70329 --- /dev/null +++ b/graphs/deep-clone-graph.py @@ -0,0 +1,60 @@ +""" +LeetCode 133. Clone Graph +https://leetcode.com/problems/clone-graph/ + +Given a reference of a node in a connected undirected graph. + +Return a deep copy (clone) of the graph. + +Each node in the graph contains a value (int) and a list (List[Node]) of its +neighbors. +""" + + +class Node: + def __init__(self, value=0, neighbors=None): + self.value = value + self.neighbors = neighbors if neighbors is not None else [] + + +def clone_graph(node: Node | None) -> None | None: + """ + This function returns a clone of a connected undirected graph. + >>> clone_graph(Node(1)) + Node(1) + >>> clone_graph(Node(1, [Node(2)])) + Node(1, [Node(2)]) + >>> clone_graph(None) + None + """ + if not node: + return None + + originals_to_clones = {} # map nodes to clones + + def create_clones(node: Node) -> None: + if node in originals_to_clones: + return + + originals_to_clones[node] = Node(node.value) + + for neighbor in node.neighbors: + create_clones(neighbor) + + create_clones(node) + + def connect_clones_to_cloned_neighbors() -> None: + for original, clone in originals_to_clones.items(): + for neighbor in original.neighbors: + cloned_neighbor = originals_to_clones[neighbor] + clone.neighbors.append(cloned_neighbor) + + connect_clones_to_cloned_neighbors() + + return originals_to_clones[node] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0d1134dac8d7f6633e49122d04e246c7a3cfa5f5 Mon Sep 17 00:00:00 2001 From: dbring Date: Wed, 4 Oct 2023 19:03:57 -0700 Subject: [PATCH 02/19] Fixed file name and removed a function call --- ...{deep-clone-graph.py => deep_clone_graph.py} | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) rename graphs/{deep-clone-graph.py => deep_clone_graph.py} (72%) diff --git a/graphs/deep-clone-graph.py b/graphs/deep_clone_graph.py similarity index 72% rename from graphs/deep-clone-graph.py rename to graphs/deep_clone_graph.py index 0ddf0cf70329..90465f4ed144 100644 --- a/graphs/deep-clone-graph.py +++ b/graphs/deep_clone_graph.py @@ -17,7 +17,7 @@ def __init__(self, value=0, neighbors=None): self.neighbors = neighbors if neighbors is not None else [] -def clone_graph(node: Node | None) -> None | None: +def clone_graph(node: Node | None) -> Node | None: """ This function returns a clone of a connected undirected graph. >>> clone_graph(Node(1)) @@ -33,6 +33,10 @@ def clone_graph(node: Node | None) -> None | None: originals_to_clones = {} # map nodes to clones def create_clones(node: Node) -> None: + """ + This helper function populates the originals_to_clones map with + the original nodes in the graph mapped to newly created clones. + """ if node in originals_to_clones: return @@ -43,13 +47,10 @@ def create_clones(node: Node) -> None: create_clones(node) - def connect_clones_to_cloned_neighbors() -> None: - for original, clone in originals_to_clones.items(): - for neighbor in original.neighbors: - cloned_neighbor = originals_to_clones[neighbor] - clone.neighbors.append(cloned_neighbor) - - connect_clones_to_cloned_neighbors() + for original, clone in originals_to_clones.items(): + for neighbor in original.neighbors: + cloned_neighbor = originals_to_clones[neighbor] + clone.neighbors.append(cloned_neighbor) return originals_to_clones[node] From 712c35e3024ea02c55aca1ac5c9c50e1f3912ec4 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 09:31:10 -0700 Subject: [PATCH 03/19] Removed nested function and fixed class parameter types --- graphs/deep_clone_graph.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 90465f4ed144..6bc09306a772 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -12,9 +12,9 @@ class Node: - def __init__(self, value=0, neighbors=None): + def __init__(self, value: int = 0, neighbors: list | None = None) -> None: self.value = value - self.neighbors = neighbors if neighbors is not None else [] + self.neighbors = neighbors or [] def clone_graph(node: Node | None) -> Node | None: @@ -32,20 +32,18 @@ def clone_graph(node: Node | None) -> Node | None: originals_to_clones = {} # map nodes to clones - def create_clones(node: Node) -> None: - """ - This helper function populates the originals_to_clones map with - the original nodes in the graph mapped to newly created clones. - """ - if node in originals_to_clones: - return + stack = [node] - originals_to_clones[node] = Node(node.value) + while stack: + original = stack.pop() - for neighbor in node.neighbors: - create_clones(neighbor) + if original in originals_to_clones: + continue - create_clones(node) + originals_to_clones[original] = Node(original.value) + + for neighbor in original.neighbors: + stack.append(neighbor) for original, clone in originals_to_clones.items(): for neighbor in original.neighbors: From ebf333a0cc486a154773ff6e2937959f6efde032 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 10:17:02 -0700 Subject: [PATCH 04/19] Fixed doctests --- graphs/deep_clone_graph.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 6bc09306a772..b8b7ab873c1d 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -16,16 +16,36 @@ def __init__(self, value: int = 0, neighbors: list | None = None) -> None: self.value = value self.neighbors = neighbors or [] + def __repr__(self): + return f"Node({self.value}, {self.neighbors})" + def clone_graph(node: Node | None) -> Node | None: """ This function returns a clone of a connected undirected graph. - >>> clone_graph(Node(1)) - Node(1) - >>> clone_graph(Node(1, [Node(2)])) - Node(1, [Node(2)]) - >>> clone_graph(None) - None + >>> node1 = Node(1) + >>> node2 = Node(2) + >>> node3 = Node(3) + >>> node4 = Node(4) + >>> node1.neighbors = [node2, node4] + >>> node2.neighbors = [node1, node3] + >>> node3.neighbors = [node2, node4] + >>> node4.neighbors = [node1, node3] + >>> clone1 = clone_graph(node1) + >>> clone1.value + 1 + >>> clone2 = clone1.neighbors[0] + >>> clone2.value + 2 + >>> clone4 = clone1.neighbors[1] + >>> clone4.value + 4 + >>> clone3 = clone2.neighbors[1] + >>> clone3.value + 3 + >>> clone = clone_graph(None) + >>> clone is None + True """ if not node: return None From bfe956b6a416998cfcb72275e5a1e0b69a4c122e Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 10:19:30 -0700 Subject: [PATCH 05/19] bug fix --- graphs/deep_clone_graph.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index b8b7ab873c1d..3f607fdce94d 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -16,9 +16,6 @@ def __init__(self, value: int = 0, neighbors: list | None = None) -> None: self.value = value self.neighbors = neighbors or [] - def __repr__(self): - return f"Node({self.value}, {self.neighbors})" - def clone_graph(node: Node | None) -> Node | None: """ From 1552e6f40d38d019e4294513e4333510646ad10f Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 10:55:33 -0700 Subject: [PATCH 06/19] Added class decorator --- graphs/deep_clone_graph.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 3f607fdce94d..b96127866273 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -9,12 +9,19 @@ Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors. """ +from dataclasses import dataclass +@dataclass class Node: - def __init__(self, value: int = 0, neighbors: list | None = None) -> None: - self.value = value - self.neighbors = neighbors or [] + value: int = 0 + neighbors: list["Node"] | None = None + + def __post_init__(self): + self.neighbors = self.neighbors or [] + + def __hash__(self): + return id(self) def clone_graph(node: Node | None) -> Node | None: @@ -24,10 +31,10 @@ def clone_graph(node: Node | None) -> Node | None: >>> node2 = Node(2) >>> node3 = Node(3) >>> node4 = Node(4) - >>> node1.neighbors = [node2, node4] - >>> node2.neighbors = [node1, node3] - >>> node3.neighbors = [node2, node4] - >>> node4.neighbors = [node1, node3] + >>> node1.neighbors.extend([node2, node4]) + >>> node2.neighbors.extend([node1, node3]) + >>> node3.neighbors.extend([node2, node4]) + >>> node4.neighbors.extend([node1, node3]) >>> clone1 = clone_graph(node1) >>> clone1.value 1 From 01b4473d28585a05280c44abce64480a6c78bf30 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 11:39:12 -0700 Subject: [PATCH 07/19] Updated doctests and fixed precommit errors --- graphs/deep_clone_graph.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index b96127866273..628ac95a5310 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -27,26 +27,10 @@ def __hash__(self): def clone_graph(node: Node | None) -> Node | None: """ This function returns a clone of a connected undirected graph. - >>> node1 = Node(1) - >>> node2 = Node(2) - >>> node3 = Node(3) - >>> node4 = Node(4) - >>> node1.neighbors.extend([node2, node4]) - >>> node2.neighbors.extend([node1, node3]) - >>> node3.neighbors.extend([node2, node4]) - >>> node4.neighbors.extend([node1, node3]) - >>> clone1 = clone_graph(node1) - >>> clone1.value - 1 - >>> clone2 = clone1.neighbors[0] - >>> clone2.value - 2 - >>> clone4 = clone1.neighbors[1] - >>> clone4.value - 4 - >>> clone3 = clone2.neighbors[1] - >>> clone3.value - 3 + >>> clone_graph(Node(1)) + Node(value=1, neighbors=[]) + >>> clone_graph(Node(1, [Node(2)])) + Node(value=1, neighbors=[Node(value=2, neighbors=[])]) >>> clone = clone_graph(None) >>> clone is None True @@ -66,12 +50,22 @@ def clone_graph(node: Node | None) -> Node | None: originals_to_clones[original] = Node(original.value) + if not original.neighbors: + continue + for neighbor in original.neighbors: stack.append(neighbor) for original, clone in originals_to_clones.items(): + if not original.neighbors: + continue + for neighbor in original.neighbors: cloned_neighbor = originals_to_clones[neighbor] + + if not clone.neighbors: + clone.neighbors = [] + clone.neighbors.append(cloned_neighbor) return originals_to_clones[node] From 88f73a5e566447a1b7bd195d7f4ca50acf7f5604 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 11:54:20 -0700 Subject: [PATCH 08/19] Cleaned up code --- graphs/deep_clone_graph.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 628ac95a5310..43bff20a1bf9 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -50,17 +50,11 @@ def clone_graph(node: Node | None) -> Node | None: originals_to_clones[original] = Node(original.value) - if not original.neighbors: - continue - - for neighbor in original.neighbors: + for neighbor in original.neighbors or []: stack.append(neighbor) for original, clone in originals_to_clones.items(): - if not original.neighbors: - continue - - for neighbor in original.neighbors: + for neighbor in original.neighbors or []: cloned_neighbor = originals_to_clones[neighbor] if not clone.neighbors: From 20f5565596b3d18f28e7b2448b076d525b1fcca5 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 12:14:56 -0700 Subject: [PATCH 09/19] Simplified doctest --- graphs/deep_clone_graph.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 43bff20a1bf9..399a9c4951f1 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -31,8 +31,7 @@ def clone_graph(node: Node | None) -> Node | None: Node(value=1, neighbors=[]) >>> clone_graph(Node(1, [Node(2)])) Node(value=1, neighbors=[Node(value=2, neighbors=[])]) - >>> clone = clone_graph(None) - >>> clone is None + >>> clone_graph(None) is None True """ if not node: From 3d92f87be8c5dcc23d5252ef5b80dab976d72896 Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 12:58:03 -0700 Subject: [PATCH 10/19] Added doctests --- graphs/deep_clone_graph.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index 399a9c4951f1..b1a0a0959d3b 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -17,10 +17,18 @@ class Node: value: int = 0 neighbors: list["Node"] | None = None - def __post_init__(self): + def __post_init__(self) -> None: + """ + >>> Node(3).neighbors + [] + """ self.neighbors = self.neighbors or [] - def __hash__(self): + def __hash__(self) -> int: + """ + >>> hash(Node(3)) != 0 + True + """ return id(self) From d4ffda81a276065160f39731047e83672e23cffb Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 14:22:07 -0700 Subject: [PATCH 11/19] Code simplification --- graphs/deep_clone_graph.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py index b1a0a0959d3b..55678b4c01ec 100644 --- a/graphs/deep_clone_graph.py +++ b/graphs/deep_clone_graph.py @@ -57,8 +57,7 @@ def clone_graph(node: Node | None) -> Node | None: originals_to_clones[original] = Node(original.value) - for neighbor in original.neighbors or []: - stack.append(neighbor) + stack.extend(original.neighbors or []) for original, clone in originals_to_clones.items(): for neighbor in original.neighbors or []: From 37f666e255bc920987d1c66a75d8dcfbb51a3cfb Mon Sep 17 00:00:00 2001 From: dbring Date: Thu, 5 Oct 2023 16:46:07 -0700 Subject: [PATCH 12/19] Created function which validates sudoku boards --- matrix/validate_sudoku_board.py | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 matrix/validate_sudoku_board.py diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py new file mode 100644 index 000000000000..7b07b5cd20b5 --- /dev/null +++ b/matrix/validate_sudoku_board.py @@ -0,0 +1,99 @@ +""" +LeetCode 36. Valid Sudoku +https://leetcode.com/problems/valid-sudoku/ +https://en.wikipedia.org/wiki/Sudoku + +Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be +validated according to the following rules: + +- Each row must contain the digits 1-9 without repetition. +- Each column must contain the digits 1-9 without repetition. +- Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 + without repetition. + +Note: + +A Sudoku board (partially filled) could be valid but is not necessarily +solvable. + +Only the filled cells need to be validated according to the mentioned rules. +""" + +from collections import defaultdict + +NUM_ROWS = 9 +NUM_COLS = 9 +EMPTY_CELL = "." +IS_VALID = True + + +def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: + """ + This function validates (but does not solve) a sudoku board. + The board may be valid but unsolvable. + + >>> is_valid_sudoku_board([ + ... ["5","3",".",".","7",".",".",".","."] + ... ,["6",".",".","1","9","5",".",".","."] + ... ,[".","9","8",".",".",".",".","6","."] + ... ,["8",".",".",".","6",".",".",".","3"] + ... ,["4",".",".","8",".","3",".",".","1"] + ... ,["7",".",".",".","2",".",".",".","6"] + ... ,[".","6",".",".",".",".","2","8","."] + ... ,[".",".",".","4","1","9",".",".","5"] + ... ,[".",".",".",".","8",".",".","7","9"] + ... ]) + True + >>> is_valid_sudoku_board([ + ... ["8","3",".",".","7",".",".",".","."] + ... ,["6",".",".","1","9","5",".",".","."] + ... ,[".","9","8",".",".",".",".","6","."] + ... ,["8",".",".",".","6",".",".",".","3"] + ... ,["4",".",".","8",".","3",".",".","1"] + ... ,["7",".",".",".","2",".",".",".","6"] + ... ,[".","6",".",".",".",".","2","8","."] + ... ,[".",".",".","4","1","9",".",".","5"] + ... ,[".",".",".",".","8",".",".","7","9"] + ... ]) + False + >>> is_valid_sudoku_board([["1", "2", "3", "4", "5", "6", "7", "8", "9"]]) + Traceback (most recent call last): + ... + Exception: Number of rows must be 9. + >>> is_valid_sudoku_board([["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]) + Traceback (most recent call last): + ... + Exception: Number of columns must be 9. + """ + if len(sudoku_board) != NUM_ROWS: + raise Exception("Number of rows must be 9.") + + for row in sudoku_board: + if len(row) != NUM_COLS: + raise Exception("Number of columns must be 9.") + + row_values = defaultdict(set) # maps each row to a set + col_values = defaultdict(set) # maps each col to a set + box_values = defaultdict(set) # maps each 3x3 box to a set + + for row in range(NUM_ROWS): + for col in range(NUM_COLS): + value = sudoku_board[row][col] + + if value == EMPTY_CELL: + continue + + box = (row // 3, col // 3) + + if ( + value in row_values[row] + or value in col_values[col] + or value in box_values[box] + ): + return not IS_VALID + + row_values[row].add(value) + col_values[col].add(value) + box_values[box].add(value) + + return IS_VALID From 7a601f522f9ea498ad8a3d6347b092e70f84036e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 6 Oct 2023 02:37:38 +0200 Subject: [PATCH 13/19] Update matrix/validate_sudoku_board.py --- matrix/validate_sudoku_board.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 7b07b5cd20b5..259181333749 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -60,7 +60,9 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: Traceback (most recent call last): ... Exception: Number of rows must be 9. - >>> is_valid_sudoku_board([["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]) + >>> is_valid_sudoku_board( + ... [["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]) + ... ) Traceback (most recent call last): ... Exception: Number of columns must be 9. From 1e9621b5e8761908ea30160f80560e596015d3f4 Mon Sep 17 00:00:00 2001 From: dbring Date: Fri, 6 Oct 2023 09:33:00 -0700 Subject: [PATCH 14/19] Fixed precommit errors --- matrix/validate_sudoku_board.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 259181333749..47d4b4b29100 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -61,7 +61,7 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: ... Exception: Number of rows must be 9. >>> is_valid_sudoku_board( - ... [["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]) + ... [["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]] ... ) Traceback (most recent call last): ... @@ -74,28 +74,28 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: if len(row) != NUM_COLS: raise Exception("Number of columns must be 9.") - row_values = defaultdict(set) # maps each row to a set - col_values = defaultdict(set) # maps each col to a set - box_values = defaultdict(set) # maps each 3x3 box to a set + row_values: defaultdict[int, set[str]] = defaultdict(set) + col_values: defaultdict[int, set[str]] = defaultdict(set) + box_values: defaultdict[tuple[int, int], set[str]] = defaultdict(set) - for row in range(NUM_ROWS): - for col in range(NUM_COLS): - value = sudoku_board[row][col] + for r in range(NUM_ROWS): + for c in range(NUM_COLS): + value = sudoku_board[r][c] if value == EMPTY_CELL: continue - box = (row // 3, col // 3) + box = (r // 3, c // 3) if ( - value in row_values[row] - or value in col_values[col] + value in row_values[r] + or value in col_values[c] or value in box_values[box] ): return not IS_VALID - row_values[row].add(value) - col_values[col].add(value) + row_values[r].add(value) + col_values[c].add(value) box_values[box].add(value) return IS_VALID From 06bfdbc15593a05ce6010b34c09b96026ef0a182 Mon Sep 17 00:00:00 2001 From: dbring Date: Fri, 6 Oct 2023 09:50:07 -0700 Subject: [PATCH 15/19] Removed file accidentally included --- graphs/deep_clone_graph.py | 77 -------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 graphs/deep_clone_graph.py diff --git a/graphs/deep_clone_graph.py b/graphs/deep_clone_graph.py deleted file mode 100644 index 55678b4c01ec..000000000000 --- a/graphs/deep_clone_graph.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -LeetCode 133. Clone Graph -https://leetcode.com/problems/clone-graph/ - -Given a reference of a node in a connected undirected graph. - -Return a deep copy (clone) of the graph. - -Each node in the graph contains a value (int) and a list (List[Node]) of its -neighbors. -""" -from dataclasses import dataclass - - -@dataclass -class Node: - value: int = 0 - neighbors: list["Node"] | None = None - - def __post_init__(self) -> None: - """ - >>> Node(3).neighbors - [] - """ - self.neighbors = self.neighbors or [] - - def __hash__(self) -> int: - """ - >>> hash(Node(3)) != 0 - True - """ - return id(self) - - -def clone_graph(node: Node | None) -> Node | None: - """ - This function returns a clone of a connected undirected graph. - >>> clone_graph(Node(1)) - Node(value=1, neighbors=[]) - >>> clone_graph(Node(1, [Node(2)])) - Node(value=1, neighbors=[Node(value=2, neighbors=[])]) - >>> clone_graph(None) is None - True - """ - if not node: - return None - - originals_to_clones = {} # map nodes to clones - - stack = [node] - - while stack: - original = stack.pop() - - if original in originals_to_clones: - continue - - originals_to_clones[original] = Node(original.value) - - stack.extend(original.neighbors or []) - - for original, clone in originals_to_clones.items(): - for neighbor in original.neighbors or []: - cloned_neighbor = originals_to_clones[neighbor] - - if not clone.neighbors: - clone.neighbors = [] - - clone.neighbors.append(cloned_neighbor) - - return originals_to_clones[node] - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From d5a338904e2236527842fb6fc46a32f4698e258e Mon Sep 17 00:00:00 2001 From: dbring Date: Mon, 9 Oct 2023 13:12:08 -0700 Subject: [PATCH 16/19] Improved readability and simplicity --- matrix/validate_sudoku_board.py | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 47d4b4b29100..3800c8b968a3 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -21,10 +21,8 @@ from collections import defaultdict -NUM_ROWS = 9 -NUM_COLS = 9 +NUM_SQUARES = 9 EMPTY_CELL = "." -IS_VALID = True def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: @@ -59,43 +57,42 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: >>> is_valid_sudoku_board([["1", "2", "3", "4", "5", "6", "7", "8", "9"]]) Traceback (most recent call last): ... - Exception: Number of rows must be 9. + ValueError: Sudoku boards must be 9x9 squares. >>> is_valid_sudoku_board( ... [["1"], ["2"], ["3"], ["4"], ["5"], ["6"], ["7"], ["8"], ["9"]] ... ) Traceback (most recent call last): ... - Exception: Number of columns must be 9. + ValueError: Sudoku boards must be 9x9 squares. """ - if len(sudoku_board) != NUM_ROWS: - raise Exception("Number of rows must be 9.") - - for row in sudoku_board: - if len(row) != NUM_COLS: - raise Exception("Number of columns must be 9.") + if len(sudoku_board) != NUM_SQUARES or ( + any(len(row) != NUM_SQUARES for row in sudoku_board) + ): + error_message = f"Sudoku boards must be {NUM_SQUARES}x{NUM_SQUARES} squares." + raise ValueError(error_message) row_values: defaultdict[int, set[str]] = defaultdict(set) col_values: defaultdict[int, set[str]] = defaultdict(set) box_values: defaultdict[tuple[int, int], set[str]] = defaultdict(set) - for r in range(NUM_ROWS): - for c in range(NUM_COLS): - value = sudoku_board[r][c] + for row in range(NUM_SQUARES): + for col in range(NUM_SQUARES): + value = sudoku_board[row][col] if value == EMPTY_CELL: continue - box = (r // 3, c // 3) + box = (row // 3, col // 3) if ( - value in row_values[r] - or value in col_values[c] + value in row_values[row] + or value in col_values[col] or value in box_values[box] ): - return not IS_VALID + return False - row_values[r].add(value) - col_values[c].add(value) + row_values[row].add(value) + col_values[col].add(value) box_values[box].add(value) - return IS_VALID + return True From 56bbe1f46de3848ad46227c666d56275346bad9a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 14 Oct 2023 17:29:46 +0200 Subject: [PATCH 17/19] Add timeit benchmarks --- matrix/validate_sudoku_board.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 3800c8b968a3..79e206d75c50 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -96,3 +96,12 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: box_values[box].add(value) return True + +if __name__ == "__main__": + print(tuple(get_boxes(one_to_nine_board))) + from doctest import testmod + from timeit import timeit + + testmod() + print(timeit("is_valid_sudoku_board(valid_board)", globals=globals())) + print(timeit("is_valid_sudoku_board(invalid_board)", globals=globals())) From 0dae81fb1565c9404f01e226ce7397607235c83d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:30:20 +0000 Subject: [PATCH 18/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/validate_sudoku_board.py | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 79e206d75c50..1d81eee67007 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -97,6 +97,7 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: return True + if __name__ == "__main__": print(tuple(get_boxes(one_to_nine_board))) from doctest import testmod From dc9ae327e0838367607a2162136af7c9213845ee Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 14 Oct 2023 17:32:17 +0200 Subject: [PATCH 19/19] Update validate_sudoku_board.py --- matrix/validate_sudoku_board.py | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix/validate_sudoku_board.py b/matrix/validate_sudoku_board.py index 1d81eee67007..0ee7b3df0b83 100644 --- a/matrix/validate_sudoku_board.py +++ b/matrix/validate_sudoku_board.py @@ -99,7 +99,6 @@ def is_valid_sudoku_board(sudoku_board: list[list[str]]) -> bool: if __name__ == "__main__": - print(tuple(get_boxes(one_to_nine_board))) from doctest import testmod from timeit import timeit