diff --git a/algorithms/arrays/remove_duplicates.py b/algorithms/arrays/remove_duplicates.py index 1c0bc0a06..661f2fddc 100644 --- a/algorithms/arrays/remove_duplicates.py +++ b/algorithms/arrays/remove_duplicates.py @@ -6,13 +6,25 @@ Input: [1, 1 ,1 ,2 ,2 ,3 ,4 ,4 ,"hey", "hey", "hello", True, True] Output: [1, 2, 3, 4, 'hey', 'hello'] + +Time Complexity: O(n) for hashable items, O(n²) worst case for unhashable items +Space Complexity: O(n) for the seen set and result array """ +from collections.abc import Hashable + + def remove_duplicates(array): + seen = set() new_array = [] for item in array: - if item not in new_array: - new_array.append(item) + if isinstance(item, Hashable): + if item not in seen: + seen.add(item) + new_array.append(item) + else: + if item not in new_array: + new_array.append(item) - return new_array \ No newline at end of file + return new_array diff --git a/algorithms/arrays/summarize_ranges.py b/algorithms/arrays/summarize_ranges.py index 58de7421a..08892e5a9 100644 --- a/algorithms/arrays/summarize_ranges.py +++ b/algorithms/arrays/summarize_ranges.py @@ -5,21 +5,22 @@ For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)]. """ +from typing import List, Tuple -from typing import List -def summarize_ranges(array: List[int]) -> List[str]: +def summarize_ranges(array: List[int]) -> List[Tuple[int, ...]]: res = [] + if len(array) == 0: + return [] if len(array) == 1: - return [str(array[0])] + return [(array[0], array[0])] it = iter(array) start = end = next(it) for num in it: if num - end == 1: end = num else: - res.append((start, end) if start != end else (start,)) + res.append((start, end)) start = end = num - res.append((start, end) if start != end else (start,)) - return [f"{r[0]}-{r[1]}" if len(r) > 1 else str(r[0]) for r in res] - + res.append((start, end)) + return res diff --git a/algorithms/graph/find_all_cliques.py b/algorithms/graph/find_all_cliques.py index f1db16ed5..c52883d55 100644 --- a/algorithms/graph/find_all_cliques.py +++ b/algorithms/graph/find_all_cliques.py @@ -4,6 +4,7 @@ the subgraph there is an edge between them). """ + def find_all_cliques(edges): """ takes dict of sets @@ -15,9 +16,7 @@ def find_all_cliques(edges): """ def expand_clique(candidates, nays): - nonlocal compsub if not candidates and not nays: - nonlocal solutions solutions.append(compsub.copy()) else: for selected in candidates.copy(): diff --git a/algorithms/greedy/__init__.py b/algorithms/greedy/__init__.py index 66184782e..ee28ff0f3 100644 --- a/algorithms/greedy/__init__.py +++ b/algorithms/greedy/__init__.py @@ -1 +1,2 @@ from .max_contiguous_subsequence_sum import * +from .gale_shapley import * \ No newline at end of file diff --git a/algorithms/greedy/gale_shapley.py b/algorithms/greedy/gale_shapley.py new file mode 100644 index 000000000..c86556074 --- /dev/null +++ b/algorithms/greedy/gale_shapley.py @@ -0,0 +1,90 @@ +""" +The Gale-Shapley algorithm is a method to solve the +stable matching/marriage problem. Given N men and women +with ranked preferences of the opposite sex, the men and +women will be matched so that each pair of man and woman +would not prefer someone over their current match. With +no conflicting preferences, the matches are stable. The +algorithm can be extended to issues that involve ranked +matching. + +Example for function: + M denotes man, W denotes woman, and number corresponds to + respective man/woman. Preference lists go from highest to lowest. + + men_preferences = { + "M1": ["W1", "W2", "W3"], + "M2": ["W1", "W3", "W2"], + "M3": ["W3", "W1", "W2"], + } + women_preferences = { + "W1": ["M2", "M1", "M3"], + "W2": ["M1", "M2", "M3"], + "W3": ["M3", "M1", "M2"], + } + + input: print(gale_shapley(men_preferences, women_preferences)) + output: {'M2': 'W1', 'M3': 'W3', 'M1': 'W2'} + Explanation: + Both Man 1 and Man 2 have a top preference of Woman 1, + and since Woman 1 has a top preference of Man 2, Man 2 + and Woman 1 are matched, and Man 1 is added back to available + men. Man 3 and Woman 3 have their top preference as each other, + so the two are matched. Man 1 then proposes to Woman 2, and + Man 1 is the top preference of Woman 2, so the two are matched. + There is no match of Man AND Woman where both would want to + leave, so the current matches are stable. + +""" + +# size denotes the number of men/women +# Function takes in dictionary for men and women preferences in style outlined above +def gale_shapley(men, women): + size = len(men) + # Initialize all men to be available + men_available = list(men.keys()) + # Initialize married to empty + married = {} + # Intialize proposal count for each man to 0 + proposal_counts = {man: 0 for man in men} + while men_available: + # Pop first available man + man = men_available.pop(0) + # Of the popped man, set woman equal to corresponding proposal index + woman = men[man][proposal_counts[man]] + #increment proposal count + proposal_counts[man] += 1 + if woman not in married: + # Set marriage if woman not married + married[woman] = man + else: + # If woman married, currently_married corresponds to currently matched man + currently_married = married[woman] + if women[woman].index(man) < women[woman].index(currently_married): + """ + If the available man is of greater preference to the woman than her + currently married partner, change the marriage to the new available + man and append the previously married man back to men_available + """ + married[woman] = man + men_available.append(currently_married) + else: + # Add man back to men_available and try woman at next index + men_available.append(man) + # Returns pairs of matched men and women in form of Man:Woman + return {man: woman for woman, man in married.items()} + +# Example case +men_preferences = { + "M1": ["W1", "W2", "W3"], + "M2": ["W1", "W3", "W2"], + "M3": ["W3", "W1", "W2"], +} +women_preferences = { + "W1": ["M2", "M1", "M3"], + "W2": ["M1", "M2", "M3"], + "W3": ["M3", "M1", "M2"], +} + +res = gale_shapley(men_preferences, women_preferences) +print(res) \ No newline at end of file diff --git a/algorithms/maths/polynomial.py b/algorithms/maths/polynomial.py index 07a0c3b8a..81faf2b7e 100644 --- a/algorithms/maths/polynomial.py +++ b/algorithms/maths/polynomial.py @@ -438,10 +438,7 @@ def __floordiv__(self, other: Union[int, float, Fraction, Monomial]): # def __truediv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial: def __truediv__(self, other: Union[int, float, Fraction, Monomial]): """ - For Polynomials, only division by a monomial - is defined. - - TODO: Implement polynomial / polynomial. + For Polynomial division, no remainder is provided. Must use poly_long_division() to capture remainder """ if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction): return self.__truediv__( Monomial({}, other) ) @@ -449,17 +446,11 @@ def __truediv__(self, other: Union[int, float, Fraction, Monomial]): poly_temp = reduce(lambda acc, val: acc + val, map(lambda x: x / other, [z for z in self.all_monomials()]), Polynomial([Monomial({}, 0)])) return poly_temp elif isinstance(other, Polynomial): - if Monomial({}, 0) in other.all_monomials(): - if len(other.all_monomials()) == 2: - temp_set = {x for x in other.all_monomials() if x != Monomial({}, 0)} - only = temp_set.pop() - return self.__truediv__(only) - elif len(other.all_monomials()) == 1: - temp_set = {x for x in other.all_monomials()} - only = temp_set.pop() - return self.__truediv__(only) - - raise ValueError('Can only divide a polynomial by an int, float, Fraction, or a Monomial.') + # Call long division + quotient, remainder = self.poly_long_division(other) + return quotient # Return just the quotient, remainder is ignored here + + raise ValueError('Can only divide a polynomial by an int, float, Fraction, Monomial, or Polynomial.') return @@ -526,7 +517,59 @@ def subs(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, f def __str__(self) -> str: """ - Get a string representation of - the polynomial. + Get a properly formatted string representation of the polynomial. + """ + sorted_monos = sorted(self.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + return ' + '.join(str(m) for m in sorted_monos if m.coeff != Fraction(0, 1)) + + def poly_long_division(self, other: 'Polynomial') -> tuple['Polynomial', 'Polynomial']: """ - return ' + '.join(str(m) for m in self.all_monomials() if m.coeff != Fraction(0, 1)) + Perform polynomial long division + Returns (quotient, remainder) + """ + if not isinstance(other, Polynomial): + raise ValueError("Can only divide by another Polynomial.") + + if len(other.all_monomials()) == 0: + raise ValueError("Cannot divide by zero polynomial.") + + quotient = Polynomial([]) + remainder = self.clone() + + divisor_monos = sorted(other.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + divisor_lead = divisor_monos[0] + + while remainder.all_monomials() and max(remainder.variables(), default=-1) >= max(other.variables(), + default=-1): + remainder_monos = sorted(remainder.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True), + reverse=True) + remainder_lead = remainder_monos[0] + + if not all(remainder_lead.variables.get(var, 0) >= divisor_lead.variables.get(var, 0) for var in + divisor_lead.variables): + break + + lead_quotient = remainder_lead / divisor_lead + quotient = quotient + Polynomial([lead_quotient]) # Convert Monomial to Polynomial + + remainder = remainder - ( + Polynomial([lead_quotient]) * other) # Convert Monomial to Polynomial before multiplication + + return quotient, remainder + +dividend = Polynomial([ + Monomial({1: 3}, 4), # 4(a_1)^3 + Monomial({1: 2}, 3), # 3(a_1)^2 + Monomial({1: 1}, -2), # -2(a_1) + Monomial({}, 5) # +5 +]) + +divisor = Polynomial([ + Monomial({1: 1}, 2), # 2(a_1) + Monomial({}, -1) # -1 +]) + +quotient = dividend / divisor +print("Quotient:", quotient) diff --git a/algorithms/sort/bead_sort.py b/algorithms/sort/bead_sort.py new file mode 100644 index 000000000..0d949fa82 --- /dev/null +++ b/algorithms/sort/bead_sort.py @@ -0,0 +1,26 @@ +""" +Bead Sort (also known as Gravity Sort) is a natural sorting algorithm that simulates how beads would settle under gravity on an abacus. It is most useful for sorting positive integers, especially when the range of numbers isn't excessively large. However, it is not a comparison-based sort and is generally impractical for large inputs due to its reliance on physical modeling. +Time Complexity +- Best Case: O(n) if the numbers are already sorted +- Average Case: O(n^2) because each bead needs to be placed and then fall under gravity +- Worst Case: O(n^2) since each bead must "fall" individually +""" + +def bead_sort(arr): + if any(num < 0 for num in arr): + raise ValueError("Bead sort only works with non-negative integers.") + + max_num = max(arr) if arr else 0 + grid = [[0] * len(arr) for _ in range(max_num)] + + # Drop beads (place beads in columns) + for col, num in enumerate(arr): + for row in range(num): + grid[row][col] = 1 + + # Let the beads "fall" (count beads in each row) + for row in grid: + sum_beads = sum(row) + for col in range(len(arr)): + row[col] = 1 if col < sum_beads else 0 + diff --git a/algorithms/tree/bst/validate_bst.py b/algorithms/tree/bst/validate_bst.py new file mode 100644 index 000000000..81dfc1952 --- /dev/null +++ b/algorithms/tree/bst/validate_bst.py @@ -0,0 +1,90 @@ +# =============================================================================== +# Validate Binary Search Tree +''' +To check if the given binary tree is a valid binary search tree (BST), we need to ensure that: + 1. The left subtree of a node contains only nodes with keys less than the node's key. + 2. The right subtree of a node contains only nodes with keys greater than the node's key. + 3. Both the left and right subtrees must also be binary search trees. +''' +# =============================================================================== + +# Tree class definition +class TreeNode: + + def __init__(self, value): + + self.val = value + self.left = None + self.right = None + +# Function to validate if a binary tree is a BST +def validate_bst(node): + ''' + Validate if a binary tree is a binary search tree (BST). + Input params : Tree Node to be validated + Returns : Tuple ( + is_bst: bool, + min_value: int | None, + max_value: int | None + ) + ''' + + # Base case: An empty tree is a valid BST + if not node: + return (True, None, None) + + # Validate the left and right subtrees + valid_left, minn_left, maxx_left = validate_bst(node.left) + valid_right, minn_right, maxx_right = validate_bst(node.right) + + # If either subtree is not valid, the whole tree is not a valid BST + if not valid_left or not valid_right: + return ( + False, + minn_left if minn_left else node.val, + maxx_right if maxx_right else node.val + ) + + # Check the current node's value against the max of the left subtree + if maxx_left is not None and maxx_left > node.val: + return ( + False, + minn_left if minn_left else node.val, + maxx_right if maxx_right else node.val + ) + + # Check the current node's value against the min of the right subtree + if minn_right is not None and minn_right < node.val: + return ( + False, + minn_left if minn_left else node.val, + maxx_right if maxx_right else node.val + ) + + # If all checks pass, the tree/subtree is a valid BST + return ( + True, + minn_left if minn_left is not None else node.val, + maxx_right if maxx_right is not None else node.val + ) + +# Example usage +if __name__ == "__main__": + # Constructing a simple binary tree + root = TreeNode(10) + root.left = TreeNode(5) + root.right = TreeNode(15) + root.right.left = TreeNode(12) + root.right.right = TreeNode(20) + + ''' + 10 + / \ + 5 15 + / \ + 12 20 + ''' + + # Validate if the constructed tree is a BST + is_bst, _, _ = validate_bst(root) + print(f"The tree is a valid BST: {is_bst}") \ No newline at end of file diff --git a/algorithms/tree/construct_tree_postorder_preorder.py b/algorithms/tree/construct_tree_postorder_preorder.py index a1bc1688a..90d109a04 100644 --- a/algorithms/tree/construct_tree_postorder_preorder.py +++ b/algorithms/tree/construct_tree_postorder_preorder.py @@ -21,87 +21,88 @@ Output: 8 4 9 2 5 1 6 3 7 """ + class TreeNode: - def __init__(self, val, left = None, right = None): + def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right + pre_index = 0 - + + def construct_tree_util(pre: list, post: list, low: int, high: int, size: int): """ - Recursive function that constructs tree from preorder and postorder array. - - preIndex is a global variable that keeps track of the index in preorder - array. - preorder and postorder array are represented are pre[] and post[] respectively. - low and high are the indices for the postorder array. + Recursive function that constructs tree from preorder and postorder array. + + preIndex is a global variable that keeps track of the index in preorder + array. + preorder and postorder array are represented are pre[] and post[] respectively. + low and high are the indices for the postorder array. """ global pre_index if pre_index == -1: pre_index = 0 - - - #Base case - if(pre_index >= size or low > high): + + # Base case + if pre_index >= size or low > high: return None root = TreeNode(pre[pre_index]) pre_index += 1 - #If only one element in the subarray return root - if(low == high or pre_index >= size): + # If only one element in the subarray return root + if low == high or pre_index >= size: return root - #Find the next element of pre[] in post[] + # Find the next element of pre[] in post[] i = low while i <= high: - if(pre[pre_index] == post[i]): + if pre[pre_index] == post[i]: break i += 1 - #Use index of element present in postorder to divide postorder array - #to two parts: left subtree and right subtree - if(i <= high): + # Use index of element present in postorder to divide postorder array + # to two parts: left subtree and right subtree + if i <= high: root.left = construct_tree_util(pre, post, low, i, size) - root.right = construct_tree_util(pre, post, i+1, high, size) + root.right = construct_tree_util(pre, post, i + 1, high, size) return root def construct_tree(pre: list, post: list, size: int): """ - Main Function that will construct the full binary tree from given preorder - and postorder array. + Main Function that will construct the full binary tree from given preorder + and postorder array. """ - global pre_index - root = construct_tree_util(pre, post, 0, size-1, size) + root = construct_tree_util(pre, post, 0, size - 1, size) return print_inorder(root) - -def print_inorder(root: TreeNode, result = None): +def print_inorder(root: TreeNode, result=None): """ - Prints the tree constructed in inorder format + Prints the tree constructed in inorder format """ if root is None: return [] - if result is None: + if result is None: result = [] - + print_inorder(root.left, result) result.append(root.val) print_inorder(root.right, result) return result -if __name__ == '__main__': + +if __name__ == "__main__": pre = [1, 2, 4, 5, 3, 6, 7] post = [4, 5, 2, 6, 7, 3, 1] size = len(pre) diff --git a/tests/test_array.py b/tests/test_array.py index f1ad11693..ae68c2a6f 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1,16 +1,25 @@ from algorithms.arrays import ( - delete_nth, delete_nth_naive, - flatten_iter, flatten, + delete_nth, + delete_nth_naive, + flatten_iter, + flatten, garage, josephus, - longest_non_repeat_v1, longest_non_repeat_v2, - get_longest_non_repeat_v1, get_longest_non_repeat_v2, - Interval, merge_intervals, + longest_non_repeat_v1, + longest_non_repeat_v2, + get_longest_non_repeat_v1, + get_longest_non_repeat_v2, + Interval, + merge_intervals, missing_ranges, move_zeros, - plus_one_v1, plus_one_v2, plus_one_v3, - remove_duplicates - rotate_v1, rotate_v2, rotate_v3, + plus_one_v1, + plus_one_v2, + plus_one_v3, + remove_duplicates, + rotate_v1, + rotate_v2, + rotate_v3, summarize_ranges, three_sum, two_sum, @@ -18,7 +27,7 @@ trimmean, top_1, limit, - n_sum + n_sum, ) import unittest @@ -28,17 +37,17 @@ class TestJosephus(unittest.TestCase): def test_josephus(self): - a = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] + a = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] josephus_generator = josephus(a, 3) - self.assertEqual(next(josephus_generator), '3') - self.assertEqual(next(josephus_generator), '6') - self.assertEqual(next(josephus_generator), '9') - self.assertEqual(next(josephus_generator), '4') - self.assertEqual(next(josephus_generator), '8') - self.assertEqual(next(josephus_generator), '5') - self.assertEqual(next(josephus_generator), '2') - self.assertEqual(next(josephus_generator), '7') - self.assertEqual(next(josephus_generator), '1') + self.assertEqual(next(josephus_generator), "3") + self.assertEqual(next(josephus_generator), "6") + self.assertEqual(next(josephus_generator), "9") + self.assertEqual(next(josephus_generator), "4") + self.assertEqual(next(josephus_generator), "8") + self.assertEqual(next(josephus_generator), "5") + self.assertEqual(next(josephus_generator), "2") + self.assertEqual(next(josephus_generator), "7") + self.assertEqual(next(josephus_generator), "1") self.assertRaises(StopIteration, next, josephus_generator) @@ -46,37 +55,37 @@ class TestDeleteNth(unittest.TestCase): def test_delete_nth_naive(self): - self.assertListEqual(delete_nth_naive( - [20, 37, 20, 21, 37, 21, 21], n=1), - [20, 37, 21]) - self.assertListEqual(delete_nth_naive( - [1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), - [1, 1, 3, 3, 7, 2, 2, 2]) - self.assertListEqual(delete_nth_naive( - [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], - n=3), - [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5]) - self.assertListEqual(delete_nth_naive([], n=5), - []) - self.assertListEqual(delete_nth_naive( - [1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], - n=0), - []) + self.assertListEqual( + delete_nth_naive([20, 37, 20, 21, 37, 21, 21], n=1), [20, 37, 21] + ) + self.assertListEqual( + delete_nth_naive([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2] + ) + self.assertListEqual( + delete_nth_naive([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), + [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5], + ) + self.assertListEqual(delete_nth_naive([], n=5), []) + self.assertListEqual( + delete_nth_naive([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), [] + ) def test_delete_nth(self): - self.assertListEqual(delete_nth([20, 37, 20, 21, 37, 21, 21], n=1), - [20, 37, 21]) - self.assertListEqual(delete_nth([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), - [1, 1, 3, 3, 7, 2, 2, 2]) - self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, - 5, 3, 1], n=3), - [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5]) - self.assertListEqual(delete_nth([], n=5), - []) - self.assertListEqual(delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, - 5, 3, 1], n=0), - []) + self.assertListEqual( + delete_nth([20, 37, 20, 21, 37, 21, 21], n=1), [20, 37, 21] + ) + self.assertListEqual( + delete_nth([1, 1, 3, 3, 7, 2, 2, 2, 2], n=3), [1, 1, 3, 3, 7, 2, 2, 2] + ) + self.assertListEqual( + delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=3), + [1, 2, 3, 1, 1, 2, 2, 3, 3, 4, 5], + ) + self.assertListEqual(delete_nth([], n=5), []) + self.assertListEqual( + delete_nth([1, 2, 3, 1, 1, 2, 1, 2, 3, 3, 2, 4, 5, 3, 1], n=0), [] + ) class TestFlatten(unittest.TestCase): @@ -134,10 +143,9 @@ def test_garage(self): steps, seq = garage(initial, final) self.assertEqual(steps, 4) - self.assertListEqual(seq, [[0, 2, 3, 1, 4], - [2, 0, 3, 1, 4], - [2, 3, 0, 1, 4], - [0, 3, 2, 1, 4]]) + self.assertListEqual( + seq, [[0, 2, 3, 1, 4], [2, 0, 3, 1, 4], [2, 3, 0, 1, 4], [0, 3, 2, 1, 4]] + ) class TestLongestNonRepeat(unittest.TestCase): @@ -178,47 +186,44 @@ def test_longest_non_repeat_v2(self): def test_get_longest_non_repeat_v1(self): string = "abcabcbb" - self.assertEqual(get_longest_non_repeat_v1(string), (3, 'abc')) + self.assertEqual(get_longest_non_repeat_v1(string), (3, "abc")) string = "bbbbb" - self.assertEqual(get_longest_non_repeat_v1(string), (1, 'b')) + self.assertEqual(get_longest_non_repeat_v1(string), (1, "b")) string = "pwwkew" - self.assertEqual(get_longest_non_repeat_v1(string), (3, 'wke')) + self.assertEqual(get_longest_non_repeat_v1(string), (3, "wke")) string = "dvdf" - self.assertEqual(get_longest_non_repeat_v1(string), (3, 'vdf')) + self.assertEqual(get_longest_non_repeat_v1(string), (3, "vdf")) string = "asjrgapa" - self.assertEqual(get_longest_non_repeat_v1(string), (6, 'sjrgap')) + self.assertEqual(get_longest_non_repeat_v1(string), (6, "sjrgap")) def test_get_longest_non_repeat_v2(self): string = "abcabcbb" - self.assertEqual(get_longest_non_repeat_v2(string), (3, 'abc')) + self.assertEqual(get_longest_non_repeat_v2(string), (3, "abc")) string = "bbbbb" - self.assertEqual(get_longest_non_repeat_v2(string), (1, 'b')) + self.assertEqual(get_longest_non_repeat_v2(string), (1, "b")) string = "pwwkew" - self.assertEqual(get_longest_non_repeat_v2(string), (3, 'wke')) + self.assertEqual(get_longest_non_repeat_v2(string), (3, "wke")) string = "dvdf" - self.assertEqual(get_longest_non_repeat_v2(string), (3, 'vdf')) + self.assertEqual(get_longest_non_repeat_v2(string), (3, "vdf")) string = "asjrgapa" - self.assertEqual(get_longest_non_repeat_v2(string), (6, 'sjrgap')) + self.assertEqual(get_longest_non_repeat_v2(string), (6, "sjrgap")) class TestMaxOnesIndex(unittest.TestCase): def test_max_ones_index(self): - self.assertEqual(9, max_ones_index([1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, - 1, 1])) - self.assertEqual(3, max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, - 1, 1])) - self.assertEqual(-1, max_ones_index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1])) + self.assertEqual(9, max_ones_index([1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])) + self.assertEqual(3, max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1])) + self.assertEqual(-1, max_ones_index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])) class TestMergeInterval(unittest.TestCase): @@ -228,17 +233,13 @@ def test_merge(self): intervals = [Interval(i[0], i[1]) for i in interval_list] merged_intervals = Interval.merge(intervals) self.assertEqual( - merged_intervals, - [Interval(1, 6), Interval(8, 10), Interval(15, 18)] + merged_intervals, [Interval(1, 6), Interval(8, 10), Interval(15, 18)] ) def test_merge_intervals(self): interval_list = [[1, 3], [2, 6], [8, 10], [15, 18]] merged_intervals = merge_intervals(interval_list) - self.assertEqual( - merged_intervals, - [[1, 6], [8, 10], [15, 18]] - ) + self.assertEqual(merged_intervals, [[1, 6], [8, 10], [15, 18]]) class TestMissingRanges(unittest.TestCase): @@ -247,24 +248,29 @@ def test_missing_ranges(self): arr = [3, 5, 10, 11, 12, 15, 19] - self.assertListEqual(missing_ranges(arr, 0, 20), - [(0, 2), (4, 4), (6, 9), - (13, 14), (16, 18), (20, 20)]) + self.assertListEqual( + missing_ranges(arr, 0, 20), + [(0, 2), (4, 4), (6, 9), (13, 14), (16, 18), (20, 20)], + ) - self.assertListEqual(missing_ranges(arr, 6, 100), - [(6, 9), (13, 14), (16, 18), (20, 100)]) + self.assertListEqual( + missing_ranges(arr, 6, 100), [(6, 9), (13, 14), (16, 18), (20, 100)] + ) class TestMoveZeros(unittest.TestCase): def test_move_zeros(self): - self.assertListEqual(move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]), - [False, 1, 1, 2, 1, 3, "a", 0, 0]) + self.assertListEqual( + move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]), + [False, 1, 1, 2, 1, 3, "a", 0, 0], + ) - self.assertListEqual(move_zeros([0, 34, 'rahul', [], None, 0, - True, 0]), - [34, 'rahul', [], None, True, 0, 0, 0]) + self.assertListEqual( + move_zeros([0, 34, "rahul", [], None, 0, True, 0]), + [34, "rahul", [], None, True, 0, 0, 0], + ) class TestPlusOne(unittest.TestCase): @@ -274,71 +280,91 @@ def test_plus_one_v1(self): self.assertListEqual(plus_one_v1([0]), [1]) self.assertListEqual(plus_one_v1([9]), [1, 0]) self.assertListEqual(plus_one_v1([1, 0, 9]), [1, 1, 0]) - self.assertListEqual(plus_one_v1([9, 9, 8, 0, 0, 9]), - [9, 9, 8, 0, 1, 0]) - self.assertListEqual(plus_one_v1([9, 9, 9, 9]), - [1, 0, 0, 0, 0]) + self.assertListEqual(plus_one_v1([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) + self.assertListEqual(plus_one_v1([9, 9, 9, 9]), [1, 0, 0, 0, 0]) def test_plus_one_v2(self): self.assertListEqual(plus_one_v2([0]), [1]) self.assertListEqual(plus_one_v2([9]), [1, 0]) self.assertListEqual(plus_one_v2([1, 0, 9]), [1, 1, 0]) - self.assertListEqual(plus_one_v2([9, 9, 8, 0, 0, 9]), - [9, 9, 8, 0, 1, 0]) - self.assertListEqual(plus_one_v2([9, 9, 9, 9]), - [1, 0, 0, 0, 0]) + self.assertListEqual(plus_one_v2([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) + self.assertListEqual(plus_one_v2([9, 9, 9, 9]), [1, 0, 0, 0, 0]) def test_plus_one_v3(self): self.assertListEqual(plus_one_v3([0]), [1]) self.assertListEqual(plus_one_v3([9]), [1, 0]) self.assertListEqual(plus_one_v3([1, 0, 9]), [1, 1, 0]) - self.assertListEqual(plus_one_v3([9, 9, 8, 0, 0, 9]), - [9, 9, 8, 0, 1, 0]) - self.assertListEqual(plus_one_v3([9, 9, 9, 9]), - [1, 0, 0, 0, 0]) + self.assertListEqual(plus_one_v3([9, 9, 8, 0, 0, 9]), [9, 9, 8, 0, 1, 0]) + self.assertListEqual(plus_one_v3([9, 9, 9, 9]), [1, 0, 0, 0, 0]) + class TestRemoveDuplicate(unittest.TestCase): def test_remove_duplicates(self): - self.assertListEqual(remove_duplicates([1,1,1,2,2,2,3,3,4,4,5,6,7,7,7,8,8,9,10,10])) - self.assertListEqual(remove_duplicates(["hey", "hello", "hello", "car", "house", "house"])) - self.assertListEqual(remove_duplicates([True, True, False, True, False, None, None])) - self.assertListEqual(remove_duplicates([1,1,"hello", "hello", True, False, False])) - self.assertListEqual(remove_duplicates([1, "hello", True, False])) + self.assertListEqual( + remove_duplicates( + [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 7, 7, 7, 8, 8, 9, 10, 10] + ), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ) + self.assertListEqual( + remove_duplicates(["hey", "hello", "hello", "car", "house", "house"]), + ["hey", "hello", "car", "house"], + ) + self.assertListEqual( + remove_duplicates([True, True, False, True, False, None, None]), + [True, False, None], + ) + self.assertListEqual( + remove_duplicates([1, 1, "hello", "hello", True, False, False]), + [1, "hello", False], + ) + self.assertListEqual( + remove_duplicates([1, "hello", True, False]), [1, "hello", False] + ) class TestRotateArray(unittest.TestCase): def test_rotate_v1(self): - self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) - self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) - self.assertListEqual(rotate_v1([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual( + rotate_v1([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] + ) + self.assertListEqual( + rotate_v1([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] + ) + self.assertListEqual( + rotate_v1([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] + ) self.assertListEqual(rotate_v1([1, 2], k=111), [2, 1]) def test_rotate_v2(self): - self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) - self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) - self.assertListEqual(rotate_v2([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual( + rotate_v2([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] + ) + self.assertListEqual( + rotate_v2([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] + ) + self.assertListEqual( + rotate_v2([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] + ) self.assertListEqual(rotate_v2([1, 2], k=111), [2, 1]) def test_rotate_v3(self): - self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=3), - [5, 6, 7, 1, 2, 3, 4]) - self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=1), - [7, 1, 2, 3, 4, 5, 6]) - self.assertListEqual(rotate_v3([1, 2, 3, 4, 5, 6, 7], k=7), - [1, 2, 3, 4, 5, 6, 7]) + self.assertListEqual( + rotate_v3([1, 2, 3, 4, 5, 6, 7], k=3), [5, 6, 7, 1, 2, 3, 4] + ) + self.assertListEqual( + rotate_v3([1, 2, 3, 4, 5, 6, 7], k=1), [7, 1, 2, 3, 4, 5, 6] + ) + self.assertListEqual( + rotate_v3([1, 2, 3, 4, 5, 6, 7], k=7), [1, 2, 3, 4, 5, 6, 7] + ) self.assertListEqual(rotate_v3([1, 2], k=111), [2, 1]) @@ -346,23 +372,24 @@ class TestSummaryRanges(unittest.TestCase): def test_summarize_ranges(self): - self.assertListEqual(summarize_ranges([0, 1, 2, 4, 5, 7]), - [(0, 2), (4, 5), (7, 7)]) - self.assertListEqual(summarize_ranges([-5, -4, -3, 1, 2, 4, 5, 6]), - [(-5, -3), (1, 2), (4, 6)]) - self.assertListEqual(summarize_ranges([-2, -1, 0, 1, 2]), - [(-2, 2)]) + self.assertListEqual( + summarize_ranges([0, 1, 2, 4, 5, 7]), [(0, 2), (4, 5), (7, 7)] + ) + self.assertListEqual( + summarize_ranges([-5, -4, -3, 1, 2, 4, 5, 6]), [(-5, -3), (1, 2), (4, 6)] + ) + self.assertListEqual(summarize_ranges([-2, -1, 0, 1, 2]), [(-2, 2)]) class TestThreeSum(unittest.TestCase): def test_three_sum(self): - self.assertSetEqual(three_sum([-1, 0, 1, 2, -1, -4]), - {(-1, 0, 1), (-1, -1, 2)}) + self.assertSetEqual(three_sum([-1, 0, 1, 2, -1, -4]), {(-1, 0, 1), (-1, -1, 2)}) - self.assertSetEqual(three_sum([-1, 3, 1, 2, -1, -4, -2]), - {(-4, 1, 3), (-2, -1, 3), (-1, -1, 2)}) + self.assertSetEqual( + three_sum([-1, 3, 1, 2, -1, -4, -2]), {(-4, 1, 3), (-2, -1, 3), (-1, -1, 2)} + ) class TestTwoSum(unittest.TestCase): @@ -380,16 +407,14 @@ class TestTrimmean(unittest.TestCase): def test_trimmean(self): self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20), 5.5) - self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 20), - 6.0) + self.assertEqual(trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 20), 6.0) class TestTop1(unittest.TestCase): def test_top_1(self): self.assertListEqual(top_1([1, 1, 2, 2, 3]), [1, 2]) - self.assertListEqual(top_1([1, 2, 3, 324, 234, 23, 23, 1, 23, 23]), - [23]) + self.assertListEqual(top_1([1, 2, 3, 324, 234, 23, 23, 1, 23, 23]), [23]) class TestLimit(unittest.TestCase): @@ -405,37 +430,115 @@ class TestNSum(unittest.TestCase): def test_n_sum(self): self.assertEqual(n_sum(2, [-3, 5, 2, 3, 8, -9], 6), []) # noqa: E501 - self.assertEqual(n_sum(3, [-5, -4, -3, -2, -1, 0, 1, 2, 3], 0), - sorted([[-5, 2, 3], [-2, 0, 2], [-4, 1, 3], - [-3, 1, 2], [-1, 0, 1], [-2, -1, 3], - [-3, 0, 3]])) # noqa: E501 - self.assertEqual(n_sum(3, [-1, 0, 1, 2, -1, -4], 0), - sorted([[-1, -1, 2], [-1, 0, 1]])) # noqa: E501 - self.assertEqual(n_sum(4, [1, 0, -1, 0, -2, 2], 0), - sorted([[-2, -1, 1, 2], [-2, 0, 0, 2], - [-1, 0, 0, 1]])) # noqa: E501 - self.assertEqual(n_sum(4, [7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, -3, -2], 10), sorted([[-6, 2, 7, 7], [-6, 3, 6, 7], [-6, 4, 5, 7], [-6, 4, 6, 6], [-5, 1, 7, 7], [-5, 2, 6, 7], [-5, 3, 5, 7], [-5, 3, 6, 6], [-5, 4, 4, 7], [-5, 4, 5, 6], [-4, 0, 7, 7], [-4, 1, 6, 7], [-4, 2, 5, 7], [-4, 2, 6, 6], [-4, 3, 4, 7], [-4, 3, 5, 6], [-4, 4, 4, 6], [-3, -1, 7, 7], [-3, 0, 6, 7], [-3, 1, 5, 7], [-3, 1, 6, 6], [-3, 2, 4, 7], [-3, 2, 5, 6], [-3, 3, 4, 6], [-3, 4, 4, 5], [-2, -2, 7, 7], [-2, -1, 6, 7], [-2, 0, 5, 7], [-2, 0, 6, 6], [-2, 1, 4, 7], [-2, 1, 5, 6], [-2, 2, 3, 7], [-2, 2, 4, 6], [-2, 3, 4, 5], [-1, 0, 4, 7], [-1, 0, 5, 6], [-1, 1, 3, 7], [-1, 1, 4, 6], [-1, 2, 3, 6], [-1, 2, 4, 5], [-1, 3, 4, 4], [0, 1, 2, 7], [0, 1, 3, 6], [0, 1, 4, 5], [0, 2, 3, 5], [0, 2, 4, 4], [1, 2, 3, 4]])) # noqa: E501 - - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], - [-9, 5]], 0, # noqa: E501 - sum_closure=lambda a, b: a[0] + b[0]), # noqa: E501 - [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]]) # noqa: E501 - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], - [-9, 5]], [0, 3], # noqa: E501 - sum_closure=lambda a, b: [a[0] + b[0], - a[1] + b[1]], # noqa: E501 - same_closure=lambda a, b: a[0] == b[0] - and a[1] == b[1]), # noqa: E501 - [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]]) # noqa: E501 - self.assertEqual(n_sum(2, [[-3, 0], [-2, 1], [2, 2], [3, 3], - [8, 4], [-9, 5]], -5, # noqa: E501 - sum_closure=lambda a, b: [a[0] + b[1], - a[1] + b[0]], # noqa: E501 - compare_closure=lambda a, b: -1 if a[0] < b - else 1 if a[0] > b else 0), # noqa: E501 - [[[-9, 5], [8, 4]]]) # noqa: E501 - - -if __name__ == '__main__': + self.assertEqual( + n_sum(3, [-5, -4, -3, -2, -1, 0, 1, 2, 3], 0), + sorted( + [ + [-5, 2, 3], + [-2, 0, 2], + [-4, 1, 3], + [-3, 1, 2], + [-1, 0, 1], + [-2, -1, 3], + [-3, 0, 3], + ] + ), + ) # noqa: E501 + self.assertEqual( + n_sum(3, [-1, 0, 1, 2, -1, -4], 0), sorted([[-1, -1, 2], [-1, 0, 1]]) + ) # noqa: E501 + self.assertEqual( + n_sum(4, [1, 0, -1, 0, -2, 2], 0), + sorted([[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]), + ) # noqa: E501 + self.assertEqual( + n_sum( + 4, [7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, -3, -2], 10 + ), + sorted( + [ + [-6, 2, 7, 7], + [-6, 3, 6, 7], + [-6, 4, 5, 7], + [-6, 4, 6, 6], + [-5, 1, 7, 7], + [-5, 2, 6, 7], + [-5, 3, 5, 7], + [-5, 3, 6, 6], + [-5, 4, 4, 7], + [-5, 4, 5, 6], + [-4, 0, 7, 7], + [-4, 1, 6, 7], + [-4, 2, 5, 7], + [-4, 2, 6, 6], + [-4, 3, 4, 7], + [-4, 3, 5, 6], + [-4, 4, 4, 6], + [-3, -1, 7, 7], + [-3, 0, 6, 7], + [-3, 1, 5, 7], + [-3, 1, 6, 6], + [-3, 2, 4, 7], + [-3, 2, 5, 6], + [-3, 3, 4, 6], + [-3, 4, 4, 5], + [-2, -2, 7, 7], + [-2, -1, 6, 7], + [-2, 0, 5, 7], + [-2, 0, 6, 6], + [-2, 1, 4, 7], + [-2, 1, 5, 6], + [-2, 2, 3, 7], + [-2, 2, 4, 6], + [-2, 3, 4, 5], + [-1, 0, 4, 7], + [-1, 0, 5, 6], + [-1, 1, 3, 7], + [-1, 1, 4, 6], + [-1, 2, 3, 6], + [-1, 2, 4, 5], + [-1, 3, 4, 4], + [0, 1, 2, 7], + [0, 1, 3, 6], + [0, 1, 4, 5], + [0, 2, 3, 5], + [0, 2, 4, 4], + [1, 2, 3, 4], + ] + ), + ) # noqa: E501 + + self.assertEqual( + n_sum( + 2, + [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], + 0, # noqa: E501 + sum_closure=lambda a, b: a[0] + b[0], + ), # noqa: E501 + [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]], + ) # noqa: E501 + self.assertEqual( + n_sum( + 2, + [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], + [0, 3], # noqa: E501 + sum_closure=lambda a, b: [a[0] + b[0], a[1] + b[1]], # noqa: E501 + same_closure=lambda a, b: a[0] == b[0] and a[1] == b[1], + ), # noqa: E501 + [[[-3, 0], [3, 3]], [[-2, 1], [2, 2]]], + ) # noqa: E501 + self.assertEqual( + n_sum( + 2, + [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]], + -5, # noqa: E501 + sum_closure=lambda a, b: [a[0] + b[1], a[1] + b[0]], # noqa: E501 + compare_closure=lambda a, b: -1 if a[0] < b else 1 if a[0] > b else 0, + ), # noqa: E501 + [[[-9, 5], [8, 4]]], + ) # noqa: E501 + + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_polynomial.py b/tests/test_polynomial.py index 4ba2ba6b7..ab5674017 100644 --- a/tests/test_polynomial.py +++ b/tests/test_polynomial.py @@ -105,27 +105,6 @@ def test_polynomial_multiplication(self): ])) return - def test_polynomial_division(self): - - # Should raise a ValueError if the divisor is not a monomial - # or a polynomial with only one term. - self.assertRaises(ValueError, lambda x, y: x / y, self.p5, self.p3) - self.assertRaises(ValueError, lambda x, y: x / y, self.p6, self.p4) - - self.assertEqual(self.p3 / self.p2, Polynomial([ - Monomial({}, 1), - Monomial({1: 1, 2: -1}, 0.75) - ])) - self.assertEqual(self.p7 / self.m1, Polynomial([ - Monomial({1: -1, 2: -3}, 2), - Monomial({1: 0, 2: -4}, 1.5) - ])) - self.assertEqual(self.p7 / self.m1, Polynomial([ - Monomial({1: -1, 2: -3}, 2), - Monomial({2: -4}, 1.5) - ])) - return - def test_polynomial_variables(self): # The zero polynomial has no variables. @@ -172,4 +151,51 @@ def test_polynomial_clone(self): self.assertEqual(self.p5.clone(), Polynomial([ Monomial({1: -1, 3: 2}, 1) ])) - return \ No newline at end of file + return + + def test_polynomial_long_division(self): + """ + Test polynomial long division + """ + + # Dividend: 4a_1^3 + 3a_1^2 - 2a_1 + 5 + dividend = Polynomial([ + Monomial({1: 3}, 4), # 4(a_1)^3 + Monomial({1: 2}, 3), # 3(a_1)^2 + Monomial({1: 1}, -2), # -2(a_1) + Monomial({}, 5) # +5 + ]) + + # Divisor: 2a_1 - 1 + divisor = Polynomial([ + Monomial({1: 1}, 2), # 2(a_1) + Monomial({}, -1) # -1 + ]) + + # Expected Quotient: 2a_1^2 + (5/2)a_1 + 1/4 + expected_quotient = Polynomial([ + Monomial({1: 2}, 2), # 2(a_1)^2 + Monomial({1: 1}, Fraction(5, 2)), # (5/2)(a_1) + Monomial({}, Fraction(1, 4)) # +1/4 + ]) + + # Expected Remainder: 21/4 + expected_remainder = Polynomial([ + Monomial({}, Fraction(21, 4)) # 21/4 + ]) + + quotient_long_div, remainder_long_div = dividend.poly_long_division(divisor) + + quotient_truediv = dividend / divisor # Calls __truediv__, which returns only the quotient + + # Check if quotient from poly_long_division matches expected + self.assertEqual(quotient_long_div, expected_quotient) + + # Check if remainder from poly_long_division matches expected + self.assertEqual(remainder_long_div, expected_remainder) + + # Check if quotient from __truediv__ matches quotient from poly_long_division + self.assertEqual(quotient_truediv, quotient_long_div) + + return +