diff --git a/DIRECTORY.md b/DIRECTORY.md index c41219e7..e0d6b823 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -284,11 +284,13 @@ * [Test Binary Search Tree Delete Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_delete_node.py) * [Test Binary Search Tree Insert](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_insert.py) * [Test Binary Search Tree Search](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/search_tree/test_binary_search_tree_search.py) + * [Test Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/test_utils.py) * Tree * [Test Binary Tree](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree.py) * [Test Binary Tree Deserialize](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_deserialize.py) * [Test Binary Tree Serialize](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_serialize.py) * [Test Binary Tree Visible Nodes](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py) + * [Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/utils.py) * Btree * [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/btree/node.py) * Heaps diff --git a/algorithms/arrays/two_sum_less_k/test_two_sum.py b/algorithms/arrays/two_sum_less_k/test_two_sum.py index 5fb56a94..574ff9a6 100644 --- a/algorithms/arrays/two_sum_less_k/test_two_sum.py +++ b/algorithms/arrays/two_sum_less_k/test_two_sum.py @@ -5,7 +5,7 @@ class TwoSumLessKTestCase(unittest.TestCase): def test_1(self): """numbers = [4,2,11,2,5,3,5,8], target = 7""" - numbers = [4,2,11,2,5,3,5,8] + numbers = [4, 2, 11, 2, 5, 3, 5, 8] target = 7 expected = 6 actual = two_sum_less_than_k(numbers, target) @@ -21,7 +21,7 @@ def test_2(self): def test_3(self): """numbers = [34,23,1,24,75,33,54,8], k = 60""" - numbers = [34,23,1,24,75,33,54,8] + numbers = [34, 23, 1, 24, 75, 33, 54, 8] k = 60 expected = 58 actual = two_sum_less_than_k(numbers, k) @@ -29,7 +29,7 @@ def test_3(self): def test_4(self): """numbers = [5,5,5,5,5,5], k = 15""" - numbers = [5,5,5,5,5,5] + numbers = [5, 5, 5, 5, 5, 5] k = 15 expected = 10 actual = two_sum_less_than_k(numbers, k) @@ -37,7 +37,7 @@ def test_4(self): def test_5(self): """numbers = [1,2,3,4,5], k = 3""" - numbers = [1,2,3,4,5] + numbers = [1, 2, 3, 4, 5] k = 3 expected = -1 actual = two_sum_less_than_k(numbers, k) diff --git a/algorithms/search/binary_search/maxruntime_n_computers/__init__.py b/algorithms/search/binary_search/maxruntime_n_computers/__init__.py index 4ec45cd5..2af12c7a 100644 --- a/algorithms/search/binary_search/maxruntime_n_computers/__init__.py +++ b/algorithms/search/binary_search/maxruntime_n_computers/__init__.py @@ -49,7 +49,6 @@ def can_run_for(batteries: List[int], n: int, target_time: int) -> bool: def max_run_time_2(batteries: List[int], n: int) -> int: - """ Finds the maximum runtime that can power the computers for the given amount of time. @@ -67,7 +66,7 @@ def max_run_time_2(batteries: List[int], n: int) -> int: usable = sum(min(b, mid) for b in batteries) if usable >= mid * n: - left = mid + left = mid else: - right = mid - 1 + right = mid - 1 return left diff --git a/algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py b/algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py index cd38e1ca..83394204 100644 --- a/algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py +++ b/algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py @@ -4,35 +4,38 @@ class MaxRunTimeTestCase(unittest.TestCase): - - @parameterized.expand([ - ([2,3,3,4], 3, 4), - ([1,1,4,5], 2, 5), - ([2,2,2,2], 1, 8), - ([7,2,5,10,8], 2, 16), - ([1,2,3,4,5], 2, 7), - ([3,4,3,4,5,5,8,2], 4, 8), - ([5,2,4], 2, 5), - ([1,6,2,6,8], 5, 1) - ]) + @parameterized.expand( + [ + ([2, 3, 3, 4], 3, 4), + ([1, 1, 4, 5], 2, 5), + ([2, 2, 2, 2], 1, 8), + ([7, 2, 5, 10, 8], 2, 16), + ([1, 2, 3, 4, 5], 2, 7), + ([3, 4, 3, 4, 5, 5, 8, 2], 4, 8), + ([5, 2, 4], 2, 5), + ([1, 6, 2, 6, 8], 5, 1), + ] + ) def test_max_runtime_1(self, batteries, n, expected): actual = max_runtime(batteries, n) self.assertEqual(expected, actual) - @parameterized.expand([ - ([2,3,3,4], 3, 4), - ([1,1,4,5], 2, 5), - ([2,2,2,2], 1, 8), - ([7,2,5,10,8], 2, 16), - ([1,2,3,4,5], 2, 7), - ([3,4,3,4,5,5,8,2], 4, 8), - ([5,2,4], 2, 5), - ([1,6,2,6,8], 5, 1) - ]) + @parameterized.expand( + [ + ([2, 3, 3, 4], 3, 4), + ([1, 1, 4, 5], 2, 5), + ([2, 2, 2, 2], 1, 8), + ([7, 2, 5, 10, 8], 2, 16), + ([1, 2, 3, 4, 5], 2, 7), + ([3, 4, 3, 4, 5, 5, 8, 2], 4, 8), + ([5, 2, 4], 2, 5), + ([1, 6, 2, 6, 8], 5, 1), + ] + ) def test_max_runtime_2(self, batteries, n, expected): actual = max_run_time_2(batteries, n) self.assertEqual(expected, actual) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/datascience/numeric_python/statistics/TestChecker.py b/datascience/numeric_python/statistics/TestChecker.py index 60d8d225..428026a5 100755 --- a/datascience/numeric_python/statistics/TestChecker.py +++ b/datascience/numeric_python/statistics/TestChecker.py @@ -8,8 +8,8 @@ def __init__(self, n): @staticmethod def test_function(actual, expected): print - "Test for " + str( - actual - ) + " passed " if actual == expected else "Test for " + str( - actual - ) + " failed, expected " + str(expected) + ( + "Test for " + str(actual) + " passed " + if actual == expected + else "Test for " + str(actual) + " failed, expected " + str(expected) + ) diff --git a/datastructures/__init__.py b/datastructures/__init__.py index 3f539b87..0b3ec3a0 100644 --- a/datastructures/__init__.py +++ b/datastructures/__init__.py @@ -1,6 +1,3 @@ from datastructures.sets import DisjointSetUnion, UnionFind -__all__ = [ - "DisjointSetUnion", - "UnionFind" -] +__all__ = ["DisjointSetUnion", "UnionFind"] diff --git a/datastructures/lists/is_sorted_how/__init__.py b/datastructures/lists/is_sorted_how/__init__.py index 7bd6a0df..b223664f 100755 --- a/datastructures/lists/is_sorted_how/__init__.py +++ b/datastructures/lists/is_sorted_how/__init__.py @@ -15,9 +15,7 @@ def is_sorted_and_how_2(arr): return ( "yes, ascending" if is_sorted_with(arr, operator.le) - else "yes, descending" - if is_sorted_with(arr, operator.ge) - else "no" + else "yes, descending" if is_sorted_with(arr, operator.ge) else "no" ) diff --git a/datastructures/sets/union_find/__init__.py b/datastructures/sets/union_find/__init__.py index cdf511dd..d5fd041c 100644 --- a/datastructures/sets/union_find/__init__.py +++ b/datastructures/sets/union_find/__init__.py @@ -47,7 +47,7 @@ def get_count(self) -> int: class UnionFind: """A minimal Union-Find data structure with path compression.""" - + def __init__(self, size: int): """Initializes the data structure with 'size' elements.""" if size <= 0: @@ -71,4 +71,4 @@ def union(self, x: int, y: int) -> bool: if root_x != root_y: self.parent[root_y] = root_x return True - return False \ No newline at end of file + return False diff --git a/datastructures/streams/stream_checker/__init__.py b/datastructures/streams/stream_checker/__init__.py index 78687c03..6ad757c9 100644 --- a/datastructures/streams/stream_checker/__init__.py +++ b/datastructures/streams/stream_checker/__init__.py @@ -4,7 +4,6 @@ class StreamChecker(object): - def __init__(self, words: List[str]): """ Initializes a StreamChecker instance. diff --git a/datastructures/streams/stream_checker/test_stream_checker.py b/datastructures/streams/stream_checker/test_stream_checker.py index 08c011c2..393e6cb4 100644 --- a/datastructures/streams/stream_checker/test_stream_checker.py +++ b/datastructures/streams/stream_checker/test_stream_checker.py @@ -31,5 +31,5 @@ def test_3(self): self.assertFalse(stream.query("b")) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/datastructures/trees/binary/README.md b/datastructures/trees/binary/README.md new file mode 100644 index 00000000..14a5aec6 --- /dev/null +++ b/datastructures/trees/binary/README.md @@ -0,0 +1,67 @@ +# Binary Trees + +## Lowest Common Ancestor of a Binary Tree + +You are given two nodes, p and q. The task is to return their lowest common ancestor (LCA). Both nodes have a reference +to their parent node. The tree’s root is not provided; you must use the parent pointers to find the nodes’ common +ancestor. + +> Note: The lowest common ancestor of two nodes, p and q, is the lowest node in the binary tree, with both p and q as +> descendants. In a tree, a descendant of a node is any node reachable by following edges downward from that node, +> including the node itself. + +Constraints + +- -10^4 ≤ `node.data` ≤ 10^4 +- The number of nodes in the tree is in the range [2, 500] +- All `node.data` are unique +- `p` != `q` +- Both `p` and `q` are present in the tree + +### Examples + +![Example 1](./images/examples/lowest_common_ancestor_example_1.png) +![Example 2](./images/examples/lowest_common_ancestor_example_2.png) +![Example 3](./images/examples/lowest_common_ancestor_example_3.png) +![Example 4](./images/examples/lowest_common_ancestor_example_4.png) + +### Solution + +This solution finds the lowest common ancestor (LCA) of two nodes in a binary tree using a smart two-pointer approach. +We start by placing one pointer at node p and the other at node q. Both pointers move up the tree at each step by +following their parent pointers. If a pointer reaches the root (i.e., its parent is None), it jumps to the other +starting node. This process continues until the two pointers meet. The key idea is that by switching starting points +after reaching the top, both pointers end up traveling the same total distance, even if p and q are at different depths. +When they meet, that meeting point is their lowest common ancestor. + +The steps of the algorithm are as follows: + +1. Initialize two pointers: ptr1 starting at p and ptr2 starting at q. +2. While ptr1 and ptr2 are not pointing to the same node: + - If ptr1 has a parent, move ptr1 to ptr1.parent; otherwise, set ptr1 = q. + - If ptr2 has a parent, move ptr2 to ptr2.parent; otherwise, set ptr2 = p. + +3. When ptr1 == ptr2, return ptr1. This node is the lowest common ancestor (LCA) of p and q. + +Let’s look at the following illustration to get a better understanding of the solution: + +![Solution 1](./images/solutions/lowest_common_ancestor_solution_1.png) +![Solution 2](./images/solutions/lowest_common_ancestor_solution_2.png) +![Solution 3](./images/solutions/lowest_common_ancestor_solution_3.png) +![Solution 4](./images/solutions/lowest_common_ancestor_solution_4.png) +![Solution 5](./images/solutions/lowest_common_ancestor_solution_5.png) +![Solution 6](./images/solutions/lowest_common_ancestor_solution_6.png) +![Solution 7](./images/solutions/lowest_common_ancestor_solution_7.png) + +### Complexity + +#### Time Complexity + +The time complexity is `O(h)` where `h` is the height of the tree, as in the worst case, each pointer might traverse the +entire height of the tree, including `h` steps. + +#### Space complexity + +The space complexity of this solution is `O(1)` as there is no additional space being used. Only two pointers are being +maintained requiring constant space. + diff --git a/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_1.png b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_1.png new file mode 100644 index 00000000..dd13c8b1 Binary files /dev/null and b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_1.png differ diff --git a/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_2.png b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_2.png new file mode 100644 index 00000000..093fd740 Binary files /dev/null and b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_2.png differ diff --git a/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_3.png b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_3.png new file mode 100644 index 00000000..887c68bd Binary files /dev/null and b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_3.png differ diff --git a/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_4.png b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_4.png new file mode 100644 index 00000000..1ece25f4 Binary files /dev/null and b/datastructures/trees/binary/images/examples/lowest_common_ancestor_example_4.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_1.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_1.png new file mode 100644 index 00000000..a64758e9 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_1.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_2.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_2.png new file mode 100644 index 00000000..45120254 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_2.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_3.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_3.png new file mode 100644 index 00000000..66dd9376 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_3.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_4.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_4.png new file mode 100644 index 00000000..87d32c08 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_4.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_5.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_5.png new file mode 100644 index 00000000..4644d353 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_5.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_6.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_6.png new file mode 100644 index 00000000..9b82773f Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_6.png differ diff --git a/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_7.png b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_7.png new file mode 100644 index 00000000..b1ed4c45 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/lowest_common_ancestor_solution_7.png differ diff --git a/datastructures/trees/binary/node.py b/datastructures/trees/binary/node.py index 6d869246..33b4efb2 100644 --- a/datastructures/trees/binary/node.py +++ b/datastructures/trees/binary/node.py @@ -5,7 +5,9 @@ class BinaryTreeNode(TreeNode): """ - Binary tree node class which will implement Binary tree + Binary tree node class which will represents a Binary tree node in a binary tree. A binary tree node only has 2 + children, named left and right, both are optional and if none exists, this is assumed to be a leaf node in a binary + tree. This allows to add a pointer to the parent node to allow for easy traversal of the tree upwards from this node """ def __init__( @@ -14,8 +16,21 @@ def __init__( left: Optional["BinaryTreeNode"] = None, right: Optional["BinaryTreeNode"] = None, key: Optional[Any] = None, - ): - super().__init__(data, key) + parent: Optional["BinaryTreeNode"] = None, + ) -> None: + """ + Constructor for BinaryTreeNode class. This will create a new node with the provided data and optional + left and right children. The key parameter is used to set a key for the node, if not provided then a hash + of the data is used. + + Args: + data (T): Value to be stored in the node + left (Optional[BinaryTreeNode]): Left child of the node + right (Optional[BinaryTreeNode]): Right child of the node + key (Optional[Any]): Key for the node, if not provided a hash of the data is used + parent (Optional[BinaryTreeNode]): Parent of the node + """ + super().__init__(data, key, parent) self.left: Optional[BinaryTreeNode] = left self.right: Optional[BinaryTreeNode] = right @@ -146,7 +161,7 @@ def insert_right(self, data: T) -> "BinaryTreeNode": return self.right @property - def children(self) -> List["BinaryTreeNode"]: + def children(self) -> List["BinaryTreeNode"] | None: """Returns children of this node. Returns: List: children of this node in a list @@ -182,3 +197,6 @@ def __eq__(self, other: "BinaryTreeNode") -> bool: return True return False + + def __hash__(self): + return hash(self.data) diff --git a/datastructures/trees/binary/search_tree/__init__.py b/datastructures/trees/binary/search_tree/__init__.py index 82642fd1..62653d81 100755 --- a/datastructures/trees/binary/search_tree/__init__.py +++ b/datastructures/trees/binary/search_tree/__init__.py @@ -13,7 +13,7 @@ def __init__(self, root: Optional[BinaryTreeNode] = None): self.stack = DynamicSizeStack() @staticmethod - def construct_bst(items: List[T]) -> Optional['BinarySearchTree']: + def construct_bst(items: List[T]) -> Optional["BinarySearchTree"]: """ Constructs a binary search tree from a sorted list of items. diff --git a/datastructures/trees/binary/test_utils.py b/datastructures/trees/binary/test_utils.py new file mode 100644 index 00000000..ed9e7fdc --- /dev/null +++ b/datastructures/trees/binary/test_utils.py @@ -0,0 +1,172 @@ +import unittest +from datastructures.trees.binary.utils import ( + lowest_common_ancestor, + lowest_common_ancestor_ptr, +) +from datastructures.trees.binary.node import BinaryTreeNode + + +class LowestCommonAncestorTestCase(unittest.TestCase): + def test_1(self): + root = BinaryTreeNode(data=10) + + # left subtree + root.left = BinaryTreeNode(data=11, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + # right subtree + root.right = BinaryTreeNode(data=22, parent=root) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + + node_one = root.left.right.left + node_two = root.left.right.right + expected = root.left.right + actual = lowest_common_ancestor(node_one, node_two) + self.assertEqual(expected, actual) + + def test_2(self): + root = BinaryTreeNode(data=10) + root.left = BinaryTreeNode(data=11, parent=root) + node_one = BinaryTreeNode(data=6, parent=root.left) + root.left.left = node_one + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + root.right = BinaryTreeNode(data=22, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + node_two = root.right.right + + expected = root + actual = lowest_common_ancestor(node_one, node_two) + self.assertEqual(expected, actual) + + def test_3(self): + root = BinaryTreeNode(data=10) + + # left subtree from root + root.left = BinaryTreeNode(data=11, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + # right subtree from root + root.right = BinaryTreeNode(data=17, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + + node_one = root.left + node_two = root.left.right.right + expected = node_one + actual = lowest_common_ancestor(node_one, node_two) + self.assertEqual(expected, actual) + + def test_4(self): + root = BinaryTreeNode(data=17) + + # left subtree from root + root.left = BinaryTreeNode(data=3, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + + # right subtree from root + root.right = BinaryTreeNode(data=16, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + + node_one = root.left.right + node_two = root.right.left + expected = root + actual = lowest_common_ancestor(node_one, node_two) + self.assertEqual(expected, actual) + + +class LowestCommonAncestorPtrTestCase(unittest.TestCase): + def test_1(self): + root = BinaryTreeNode(data=10) + + # left subtree + root.left = BinaryTreeNode(data=11, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + # right subtree + root.right = BinaryTreeNode(data=22, parent=root) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + + node_one = root.left.right.left + node_two = root.left.right.right + expected = root.left.right + actual = lowest_common_ancestor_ptr(node_one, node_two) + self.assertEqual(expected, actual) + + def test_2(self): + root = BinaryTreeNode(data=10) + root.left = BinaryTreeNode(data=11, parent=root) + node_one = BinaryTreeNode(data=6, parent=root.left) + root.left.left = node_one + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + root.right = BinaryTreeNode(data=22, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + node_two = root.right.right + + expected = root + actual = lowest_common_ancestor_ptr(node_one, node_two) + self.assertEqual(expected, actual) + + def test_3(self): + root = BinaryTreeNode(data=10) + + # left subtree from root + root.left = BinaryTreeNode(data=11, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + root.left.right.left = BinaryTreeNode(data=13, parent=root.left.right) + root.left.right.right = BinaryTreeNode(data=15, parent=root.left.right) + + # right subtree from root + root.right = BinaryTreeNode(data=17, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + + node_one = root.left + node_two = root.left.right.right + expected = node_one + actual = lowest_common_ancestor_ptr(node_one, node_two) + self.assertEqual(expected, actual) + + def test_4(self): + root = BinaryTreeNode(data=17) + + # left subtree from root + root.left = BinaryTreeNode(data=3, parent=root) + root.left.left = BinaryTreeNode(data=6, parent=root.left) + root.left.right = BinaryTreeNode(data=5, parent=root.left) + + # right subtree from root + root.right = BinaryTreeNode(data=16, parent=root) + root.right.left = BinaryTreeNode(data=19, parent=root.right) + root.right.right = BinaryTreeNode(data=14, parent=root.right) + + node_one = root.left.right + node_two = root.right.left + expected = root + actual = lowest_common_ancestor_ptr(node_one, node_two) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/trees/binary/tree/__init__.py b/datastructures/trees/binary/tree/__init__.py index 491034df..2b8e0df3 100644 --- a/datastructures/trees/binary/tree/__init__.py +++ b/datastructures/trees/binary/tree/__init__.py @@ -650,7 +650,9 @@ def longest_zig_zag_stack(self) -> int: return 0 path_length = 0 - stack: List[Tuple[BinaryTreeNode | None, int, str | None]] = [(self.root, 0, None)] + stack: List[Tuple[BinaryTreeNode | None, int, str | None]] = [ + (self.root, 0, None) + ] while stack: node, length, last = stack.pop() diff --git a/datastructures/trees/binary/utils.py b/datastructures/trees/binary/utils.py new file mode 100644 index 00000000..db2a8a82 --- /dev/null +++ b/datastructures/trees/binary/utils.py @@ -0,0 +1,69 @@ +from datastructures.trees.binary.node import BinaryTreeNode + + +def lowest_common_ancestor( + node_one: BinaryTreeNode, node_two: BinaryTreeNode +) -> BinaryTreeNode | None: + """ + Returns the lowest common ancestor of 2 nodes in the Binary Tree. + The lowest common ancestor of 2 nodes is the node farthest from the root that is an ancestor of both nodes. + This function first collects all ancestors of node_one by traversing upwards from node_one to the root, collecting + all nodes. + Then, it performs a depth-first search from node_two, checking each ancestor. The first node it finds that's already + in its set is the lowest common ancestor. + If no lowest common ancestor is found, None is returned. + + :param node_one: BinaryTreeNode + :param node_two: BinaryTreeNode + :return: BinaryTreeNode | None + """ + # create a set to store all ancestors of node_one. We traverse upward from node_one to the root, collecting all nodes + ancestors = set() + + current = node_one + while current is not None: + # add the current node to the ancestor set + ancestors.add(current) + # move up to the parent + current = current.parent + + # Now perform DFS from node_two, checking each ancestor. The first node we find that's already in our set is the + # LCA + current = node_two + while current is not None: + if current in ancestors: + # This is the first common ancestor we've found. Since we're traversing from bottom to top, this is the + # lowest common ancestor + return current + # move up to the parent + current = current.parent + + # given the constraints, this should not happen + return None + + +def lowest_common_ancestor_ptr( + node_one: BinaryTreeNode, node_two: BinaryTreeNode +) -> BinaryTreeNode | None: + """ + Returns the lowest common ancestor of 2 nodes in the Binary Tree using a two-pointer approach. + + This algorithm uses two pointers starting at node_one and node_two. Both pointers move up the tree + via parent pointers. When a pointer reaches the root (parent is None), it switches to the other + starting node. By switching starting points, both pointers travel the same total distance and meet + at the lowest common ancestor. + + Time Complexity: O(h) where h is the height of the tree + Space Complexity: O(1) as only two pointers are used + + :param node_one: BinaryTreeNode + :param node_two: BinaryTreeNode + :return: BinaryTreeNode | None - The lowest common ancestor, or None if not found + """ + ptr1, ptr2 = node_one, node_two + + while ptr1 != ptr2: + ptr1 = ptr1.parent if ptr1.parent else node_two + ptr2 = ptr2.parent if ptr2.parent else node_one + + return ptr1 diff --git a/datastructures/trees/node.py b/datastructures/trees/node.py index fd4b26eb..a1252ffb 100644 --- a/datastructures/trees/node.py +++ b/datastructures/trees/node.py @@ -12,13 +12,45 @@ class TreeNode(Generic[T]): of nodes """ - def __init__(self, value: T, key: Optional[Any] = None): + def __init__( + self, value: T, key: Optional[Any] = None, parent: Optional["TreeNode"] = None + ): """ + Initialises a tree node with a value, key and a parent node. Value here can be anything and the key can be of any type. If not key is provided, then a hash of the data is used. + Args: + value (T): The value of the node + key (Optional[Any]): The key of the node. If not provided, a hash of the data is used. + parent (Optional[TreeNode]): The parent node of the current node. + + Notes: + The key is used to identify the node in the tree. If not key is provided, a hash of the data is used. """ self.data = value self.key = key or hash(value) + self.parent = parent def __repr__(self): return f"TreeNode(data={self.data}, key={self.key})" + + def __eq__(self, other: "TreeNode[T]") -> bool: + """Checks if this node is equal to another node based on the data they contain + Args: + other(TreeNode): the other node to compare this node to + Returns: + bool: True if this node and the other node are equal, False otherwise + """ + if other is None: + return False + + if not isinstance(other, TreeNode): + return False + + if other.data == self.data: + return True + + return False + + def __hash__(self): + return hash(self.data) diff --git a/datastructures/trees/trie/__init__.py b/datastructures/trees/trie/__init__.py index 0e511794..2d180273 100644 --- a/datastructures/trees/trie/__init__.py +++ b/datastructures/trees/trie/__init__.py @@ -2,7 +2,4 @@ from datastructures.trees.trie.trie import Trie -__all__ = [ - "Trie", - "TrieNode" -] +__all__ = ["Trie", "TrieNode"] diff --git a/machine_learning/soccer_predictions/features.py b/machine_learning/soccer_predictions/features.py index 3e019243..0396c9fd 100755 --- a/machine_learning/soccer_predictions/features.py +++ b/machine_learning/soccer_predictions/features.py @@ -78,7 +78,9 @@ ON cur.matchid = opp.matchid WHERE cur.teamid != opp.teamid ORDER BY cur.matchid, cur.teamid - """ % {"team_game_summary": match_stats.team_game_summary_query()} + """ % { + "team_game_summary": match_stats.team_game_summary_query() +} def get_match_history(history_size): @@ -284,7 +286,9 @@ def get_wc_history_query(history_size): return """ SELECT * FROM (%(history_query)s) WHERE competitionid = 4 ORDER BY timestamp DESC, matchid, is_home - """ % {"history_query": get_history_query(history_size)} + """ % { + "history_query": get_history_query(history_size) + } def get_wc_features(history_size): diff --git a/machine_learning/soccer_predictions/match_stats.py b/machine_learning/soccer_predictions/match_stats.py index 1b1ada73..88f36414 100755 --- a/machine_learning/soccer_predictions/match_stats.py +++ b/machine_learning/soccer_predictions/match_stats.py @@ -65,7 +65,9 @@ WHERE dist < 40) GROUP BY matchid, teamid ORDER BY matchid, teamid - """ % {"touch_table": _TOUCH_TABLE} + """ % { + "touch_table": _TOUCH_TABLE +} # Subquery to compute raw number of goals scored. Does not take # into account own-goals (i.e. if a player scores an own-goal against diff --git a/poetry.lock b/poetry.lock index 3e371be6..dc0ed8b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,68 @@ # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +[[package]] +name = "black" +version = "25.11.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, + {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, + {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, + {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, + {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, + {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, + {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, + {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, + {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, + {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, + {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, + {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, + {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, + {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, + {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, + {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, + {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, + {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, + {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, + {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, + {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, + {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, + {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, + {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, + {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, + {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +pytokens = ">=0.3.0" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.3.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +files = [ + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -22,6 +85,17 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + [[package]] name = "packaging" version = "24.2" @@ -47,6 +121,33 @@ files = [ [package.extras] dev = ["jinja2"] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +files = [ + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, +] + +[package.extras] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] + [[package]] name = "pluggy" version = "1.5.0" @@ -113,6 +214,20 @@ aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] histogram = ["pygal", "pygaljs", "setuptools"] +[[package]] +name = "pytokens" +version = "0.3.0" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, + {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] name = "ruff" version = "0.3.7" @@ -142,4 +257,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "c189df998ea76b1405876bb79789fe9a3a4213153d7a8d74ac81b34eab3af1e3" +content-hash = "4484a420e200cd1d0c3cdcfe56e39afed7dcc2666889169211e9742516ef6c07" diff --git a/puzzles/graphs/evaluate_division/test_evaluate_division.py b/puzzles/graphs/evaluate_division/test_evaluate_division.py index ea568c37..e0783e2e 100644 --- a/puzzles/graphs/evaluate_division/test_evaluate_division.py +++ b/puzzles/graphs/evaluate_division/test_evaluate_division.py @@ -6,7 +6,8 @@ class EvaluateDivisionTestCase(unittest.TestCase): def test_1(self): """should return [6.00000,0.50000,-1.00000,1.00000,-1.00000] from equations = [["a","b"],["b","c"]], - values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]]""" + values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]] + """ expected = [6.00000, 0.50000, -1.00000, 1.00000, -1.00000] equations = [["a", "b"], ["b", "c"]] values = [2.0, 3.0] @@ -16,7 +17,8 @@ def test_1(self): def test_2(self): """should return [3.75000,0.40000,5.00000,0.20000] from equations = [["a","b"],["b","c"],["bc","cd"]], - values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]]""" + values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]] + """ expected = [3.75000, 0.40000, 5.00000, 0.20000] equations = [["a", "b"], ["b", "c"], ["bc", "cd"]] values = [1.5, 2.5, 5.0] diff --git a/puzzles/heap/maximal_score_after_k_operations/test_maximal_score.py b/puzzles/heap/maximal_score_after_k_operations/test_maximal_score.py index 8890f4c4..38c51cb7 100644 --- a/puzzles/heap/maximal_score_after_k_operations/test_maximal_score.py +++ b/puzzles/heap/maximal_score_after_k_operations/test_maximal_score.py @@ -4,74 +4,175 @@ class MaximalScoreAfterKOperationsTestCase(unittest.TestCase): def test_1(self): - nums = [10,20,30,40,50] + nums = [10, 20, 30, 40, 50] k = 4 expected = 140 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_2(self): - nums = [5,12,7,3,10] + nums = [5, 12, 7, 3, 10] k = 3 expected = 29 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_3(self): - nums = [6,9,15] + nums = [6, 9, 15] k = 2 expected = 24 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_4(self): - nums = [1,10,3,3,3] + nums = [1, 10, 3, 3, 3] k = 3 expected = 17 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_5(self): - nums = [7,10,16] + nums = [7, 10, 16] k = 2 expected = 26 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_6(self): - nums = [5,120,7,30,10] + nums = [5, 120, 7, 30, 10] k = 3 expected = 190 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_7(self): - nums = [100,200,300,400,500] + nums = [100, 200, 300, 400, 500] k = 4 expected = 1400 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_8(self): - nums = [20,20,20,20] + nums = [20, 20, 20, 20] k = 3 expected = 60 actual = max_score(nums, k) self.assertEqual(expected, actual) def test_9(self): - nums = [81698,68947,77662,46592,13226,37325,2800,22504,99833,77083,38068,40934,3640,33631,84634,66457,21309, - 64949,94392,3553,68692,31662,17348,42805,32143,7099,88341,65391,8164,65035,22205,88755,80232,84970, - 19213,36774,33975,47386,74761,4893,9040,8263,60379,88511,49040,89068,72601,17683,17871,46156,2805,10247, - 54658,27427,51671,81935,59171,70215,56400,83874,9230,31194,98266,84404,1200,89589,70329,39209,19461, - 19022,86927,26496,27561,96403,78150,47498,5696,78065,75672,44842,64855,19760,57351,7788,41209,89214, - 24315,6398,60738,88636,71885,44987,28782,13700,78965,47534,82496,66162,89596,3646,73107,13112,28574, - 37445,14997,98860] + nums = [ + 81698, + 68947, + 77662, + 46592, + 13226, + 37325, + 2800, + 22504, + 99833, + 77083, + 38068, + 40934, + 3640, + 33631, + 84634, + 66457, + 21309, + 64949, + 94392, + 3553, + 68692, + 31662, + 17348, + 42805, + 32143, + 7099, + 88341, + 65391, + 8164, + 65035, + 22205, + 88755, + 80232, + 84970, + 19213, + 36774, + 33975, + 47386, + 74761, + 4893, + 9040, + 8263, + 60379, + 88511, + 49040, + 89068, + 72601, + 17683, + 17871, + 46156, + 2805, + 10247, + 54658, + 27427, + 51671, + 81935, + 59171, + 70215, + 56400, + 83874, + 9230, + 31194, + 98266, + 84404, + 1200, + 89589, + 70329, + 39209, + 19461, + 19022, + 86927, + 26496, + 27561, + 96403, + 78150, + 47498, + 5696, + 78065, + 75672, + 44842, + 64855, + 19760, + 57351, + 7788, + 41209, + 89214, + 24315, + 6398, + 60738, + 88636, + 71885, + 44987, + 28782, + 13700, + 78965, + 47534, + 82496, + 66162, + 89596, + 3646, + 73107, + 13112, + 28574, + 37445, + 14997, + 98860, + ] k = 1000 expected = 7709375 actual = max_score(nums, k) self.assertEqual(expected, actual) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/puzzles/poker/__init__.py b/puzzles/poker/__init__.py index 3a199bef..506b159d 100755 --- a/puzzles/poker/__init__.py +++ b/puzzles/poker/__init__.py @@ -24,24 +24,38 @@ def hand_rank(hand): straight = (len(counts) == 5) and (max(ranks) - min(ranks) == 4) flush = len(set([s for r, s in hand])) == 1 return ( - 9 - if counts == (5,) - else 8 - if straight and flush - else 7 - if counts == (4, 1) - else 6 - if counts == (3, 2) - else 5 - if flush - else 4 - if straight - else 3 - if counts == (3, 1, 1) - else 2 - if counts == (2, 2, 1) - else 1 - if counts == (2, 1, 1, 1) - else 0, + ( + 9 + if counts == (5,) + else ( + 8 + if straight and flush + else ( + 7 + if counts == (4, 1) + else ( + 6 + if counts == (3, 2) + else ( + 5 + if flush + else ( + 4 + if straight + else ( + 3 + if counts == (3, 1, 1) + else ( + 2 + if counts == (2, 2, 1) + else 1 if counts == (2, 1, 1, 1) else 0 + ) + ) + ) + ) + ) + ) + ) + ), ranks, ) diff --git a/pyproject.toml b/pyproject.toml index 87c9b379..1ca20869 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pythonsnips" version = "0.1.0" -description = "" +description = "Algorithms, data structures and code snippets written in Python" authors = ["BrianLusina <12752833+BrianLusina@users.noreply.github.com>"] license = "MIT" readme = "README.md" @@ -12,6 +12,7 @@ parameterized = "^0.9.0" [tool.poetry.group.dev.dependencies] ruff = "^0.3.5" +black = "^25.11.0" [tool.poetry.group.test.dependencies] pytest-benchmark = "^5.1.0" diff --git a/pystrings/hexadecimal/__init__.py b/pystrings/hexadecimal/__init__.py index 4d46d284..01109be0 100755 --- a/pystrings/hexadecimal/__init__.py +++ b/pystrings/hexadecimal/__init__.py @@ -28,9 +28,11 @@ def hexa_first_principles(hexadecimal): # if c in hexdigits[10: len(hexdigits) - 6] is equivalent to abcdef # converts each character in hexa to digit hex_list = [ - ord(c) - ord("a") + 10 - if c in hexdigits[10 : len(hexdigits) - 6] - else ord(c) - ord("0") + ( + ord(c) - ord("a") + 10 + if c in hexdigits[10 : len(hexdigits) - 6] + else ord(c) - ord("0") + ) for c in hexa ] diff --git a/pystrings/is_prefix/test_is_prefix_of_word.py b/pystrings/is_prefix/test_is_prefix_of_word.py index fe402595..27c64c85 100644 --- a/pystrings/is_prefix/test_is_prefix_of_word.py +++ b/pystrings/is_prefix/test_is_prefix_of_word.py @@ -53,5 +53,5 @@ def test_7(self): self.assertEqual(expected, actual) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/pystrings/longest_self_contained_substring/__init__.py b/pystrings/longest_self_contained_substring/__init__.py index 6a9a9b8f..b429c316 100644 --- a/pystrings/longest_self_contained_substring/__init__.py +++ b/pystrings/longest_self_contained_substring/__init__.py @@ -45,7 +45,7 @@ def longest_self_contained_substring(s: str) -> int: continue # Check if this substring is self-contained - substring = s[start:end + 1] + substring = s[start : end + 1] is_self_contained = True # For each character in the substring, verify it doesn't appear outside diff --git a/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py b/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py index 7b7683c0..5673ae36 100644 --- a/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py +++ b/pystrings/longest_self_contained_substring/test_longest_self_contained_substring.py @@ -138,5 +138,5 @@ def test_11(self): self.assertEqual(expected, actual) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/pystrings/similar_string_groups/__init__.py b/pystrings/similar_string_groups/__init__.py index 84837a39..0a4bdb91 100644 --- a/pystrings/similar_string_groups/__init__.py +++ b/pystrings/similar_string_groups/__init__.py @@ -52,6 +52,7 @@ def is_similar(s1: str, s2: str) -> bool: # The final count of disjoint sets is the number of groups return uf.get_count() + # Helper: Decide if two strings are similar def are_similar(s1, s2): diff = [] @@ -61,9 +62,8 @@ def are_similar(s1, s2): if len(diff) > 2: return False - return (len(diff) == 0) or ( - len(diff) == 2 and diff[0] == diff[1][::-1] - ) + return (len(diff) == 0) or (len(diff) == 2 and diff[0] == diff[1][::-1]) + def num_similar_groups_2(strs: List[str]) -> int: n = len(strs) diff --git a/pystrings/similar_string_groups/test_similar_string_groups.py b/pystrings/similar_string_groups/test_similar_string_groups.py index 29a23234..6752ccd6 100644 --- a/pystrings/similar_string_groups/test_similar_string_groups.py +++ b/pystrings/similar_string_groups/test_similar_string_groups.py @@ -22,8 +22,14 @@ def test_3(self): self.assertEqual(expected, actual) def test_4(self): - strs = ["fgtdvepeqcfajhlzkwlpuhrwfcueqfbs","fgcdvppeqcfajhlzkwluehrwftuefqbs","fgtdvepeqcfajhlzkwlpuhrwfcuefqbs", - "fgcdvepeqcfajhlzkwluphrwftuefqbs","fgldvepeqcfajhlzkwcuphrwftuefqbs","fgtdvefeqcpajhlzkwlpuhrwfcuefqbs"] + strs = [ + "fgtdvepeqcfajhlzkwlpuhrwfcueqfbs", + "fgcdvppeqcfajhlzkwluehrwftuefqbs", + "fgtdvepeqcfajhlzkwlpuhrwfcuefqbs", + "fgcdvepeqcfajhlzkwluphrwftuefqbs", + "fgldvepeqcfajhlzkwcuphrwftuefqbs", + "fgtdvefeqcpajhlzkwlpuhrwfcuefqbs", + ] expected = 2 actual = num_similar_groups(strs) self.assertEqual(expected, actual) @@ -49,12 +55,18 @@ def test_3(self): self.assertEqual(expected, actual) def test_4(self): - strs = ["fgtdvepeqcfajhlzkwlpuhrwfcueqfbs","fgcdvppeqcfajhlzkwluehrwftuefqbs","fgtdvepeqcfajhlzkwlpuhrwfcuefqbs", - "fgcdvepeqcfajhlzkwluphrwftuefqbs","fgldvepeqcfajhlzkwcuphrwftuefqbs","fgtdvefeqcpajhlzkwlpuhrwfcuefqbs"] + strs = [ + "fgtdvepeqcfajhlzkwlpuhrwfcueqfbs", + "fgcdvppeqcfajhlzkwluehrwftuefqbs", + "fgtdvepeqcfajhlzkwlpuhrwfcuefqbs", + "fgcdvepeqcfajhlzkwluphrwftuefqbs", + "fgldvepeqcfajhlzkwcuphrwftuefqbs", + "fgtdvefeqcpajhlzkwlpuhrwfcuefqbs", + ] expected = 2 actual = num_similar_groups_2(strs) self.assertEqual(expected, actual) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/utils/basic/list1.py b/utils/basic/list1.py index 31722727..cea79acb 100755 --- a/utils/basic/list1.py +++ b/utils/basic/list1.py @@ -15,6 +15,7 @@ # It's ok if you do not complete all the functions, and there # are some additional functions to try in list2.py. + # A. match_ends # Given a list of strings, return the count of the number of # strings where the string length is 2 or more and the first diff --git a/utils/basic/list2.py b/utils/basic/list2.py index 7034a9e7..c6b2e8a2 100755 --- a/utils/basic/list2.py +++ b/utils/basic/list2.py @@ -8,6 +8,7 @@ # Additional basic list exercises + # D. Given a list of numbers, return a list where # all adjacent == elements have been reduced to a single element, # so [1, 2, 2, 3] returns [1, 2, 3]. You may create a new list or diff --git a/utils/basic/solution/list1.py b/utils/basic/solution/list1.py index e05b04d3..e5b48de5 100755 --- a/utils/basic/solution/list1.py +++ b/utils/basic/solution/list1.py @@ -15,6 +15,7 @@ # It's ok if you do not complete all the functions, and there # are some additional functions to try in list2.py. + # A. match_ends # Given a list of strings, return the count of the number of # strings where the string length is 2 or more and the first diff --git a/utils/basic/solution/list2.py b/utils/basic/solution/list2.py index 851dc629..5f4ad3f6 100755 --- a/utils/basic/solution/list2.py +++ b/utils/basic/solution/list2.py @@ -8,6 +8,7 @@ # Additional basic list exercises + # D. Given a list of numbers, return a list where # all adjacent == elements have been reduced to a single element, # so [1, 2, 2, 3] returns [1, 2, 3]. You may create a new list or diff --git a/utils/basic/solution/string2.py b/utils/basic/solution/string2.py index e4315de9..744ffac6 100755 --- a/utils/basic/solution/string2.py +++ b/utils/basic/solution/string2.py @@ -8,6 +8,7 @@ # Additional basic string exercises + # D. verbing # Given a string, if its length is at least 3, # add 'ing' to its end. diff --git a/utils/basic/string2.py b/utils/basic/string2.py index 578140c1..6a81dd37 100755 --- a/utils/basic/string2.py +++ b/utils/basic/string2.py @@ -8,6 +8,7 @@ # Additional basic string exercises + # D. verbing # Given a string, if its length is at least 3, # add 'ing' to its end. diff --git a/utils/file_system/__init__.py b/utils/file_system/__init__.py index 1c2e3a88..b8f42929 100755 --- a/utils/file_system/__init__.py +++ b/utils/file_system/__init__.py @@ -41,7 +41,8 @@ def obtain_dir_basenames(self): :return: dict with the key as the directory name and value as the base name """ return { - "Dir: " + os.path.dirname(self.make_absolute_path()): "Base" + "Dir: " + + os.path.dirname(self.make_absolute_path()): "Base" + os.path.basename(self.make_absolute_path()) }