diff --git a/algorithms/__init__.py b/algorithms/__init__.py index 79adb381f..1ad635bd7 100644 --- a/algorithms/__init__.py +++ b/algorithms/__init__.py @@ -7,7 +7,7 @@ >>> from algorithms.graph import dijkstra """ -import algorithms.data_structures as data_structures # noqa: F401 +from algorithms import data_structures from algorithms.common import Graph, ListNode, TreeNode -__all__ = ["TreeNode", "ListNode", "Graph", "data_structures"] +__all__ = ["Graph", "ListNode", "TreeNode", "data_structures"] diff --git a/algorithms/array/__init__.py b/algorithms/array/__init__.py index 0042a0326..c52309fbd 100644 --- a/algorithms/array/__init__.py +++ b/algorithms/array/__init__.py @@ -25,20 +25,20 @@ from .two_sum import two_sum __all__ = [ + "Interval", "delete_nth", "delete_nth_naive", "flatten", "flatten_iter", "garage", - "josephus", - "limit", "get_longest_non_repeat_v1", "get_longest_non_repeat_v2", "get_longest_non_repeat_v3", + "josephus", + "limit", "longest_non_repeat_v1", "longest_non_repeat_v2", "max_ones_index", - "Interval", "merge_intervals", "missing_ranges", "move_zeros", diff --git a/algorithms/array/delete_nth.py b/algorithms/array/delete_nth.py index f548fdea8..777ca7185 100644 --- a/algorithms/array/delete_nth.py +++ b/algorithms/array/delete_nth.py @@ -1,5 +1,4 @@ -""" -Delete Nth Occurrence +"""Delete Nth Occurrence. Given a list and a number N, create a new list that contains each element of the original list at most N times, without reordering. @@ -33,6 +32,7 @@ def delete_nth_naive(array: list[int], n: int) -> list[int]: Examples: >>> delete_nth_naive([1, 2, 3, 1, 2, 1, 2, 3], 2) [1, 2, 3, 1, 2, 3] + """ result = [] for num in array: @@ -54,6 +54,7 @@ def delete_nth(array: list[int], n: int) -> list[int]: Examples: >>> delete_nth([1, 2, 3, 1, 2, 1, 2, 3], 2) [1, 2, 3, 1, 2, 3] + """ result = [] counts = collections.defaultdict(int) diff --git a/algorithms/array/flatten.py b/algorithms/array/flatten.py index 4c6b53d30..e58defafa 100644 --- a/algorithms/array/flatten.py +++ b/algorithms/array/flatten.py @@ -1,5 +1,4 @@ -""" -Flatten Arrays +"""Flatten Arrays. Given an array that may contain nested arrays, produce a single flat resultant array. @@ -30,6 +29,7 @@ def flatten(input_arr: Iterable[Any], output_arr: list[Any] | None = None) -> li Examples: >>> flatten([2, 1, [3, [4, 5], 6], 7, [8]]) [2, 1, 3, 4, 5, 6, 7, 8] + """ if output_arr is None: output_arr = [] @@ -53,6 +53,7 @@ def flatten_iter(iterable: Iterable[Any]) -> Generator[Any, None, None]: Examples: >>> list(flatten_iter([2, 1, [3, [4, 5], 6], 7, [8]])) [2, 1, 3, 4, 5, 6, 7, 8] + """ for element in iterable: if not isinstance(element, str) and isinstance(element, Iterable): diff --git a/algorithms/array/garage.py b/algorithms/array/garage.py index 6a80cdd94..c996b4a7c 100644 --- a/algorithms/array/garage.py +++ b/algorithms/array/garage.py @@ -1,5 +1,4 @@ -""" -Garage Parking Rearrangement +"""Garage Parking Rearrangement. There is a parking lot with only one empty spot (represented by 0). Given the initial and final states, find the minimum number of moves to rearrange the lot. @@ -29,6 +28,7 @@ def garage(initial: list[int], final: list[int]) -> tuple[int, list[list[int]]]: Examples: >>> garage([1, 2, 3, 0, 4], [0, 3, 2, 1, 4]) (4, [[0, 2, 3, 1, 4], [2, 0, 3, 1, 4], [2, 3, 0, 1, 4], [0, 3, 2, 1, 4]]) + """ current = initial[::] sequence = [] diff --git a/algorithms/array/josephus.py b/algorithms/array/josephus.py index 8d808ab0f..cf9adf48b 100644 --- a/algorithms/array/josephus.py +++ b/algorithms/array/josephus.py @@ -1,5 +1,4 @@ -""" -Josephus Problem +"""Josephus Problem. People sit in a circular fashion; every k-th person is eliminated until everyone has been removed. Yield the elimination order. @@ -13,8 +12,10 @@ from __future__ import annotations -from collections.abc import Generator -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Generator def josephus(items: list[Any], skip: int) -> Generator[Any, None, None]: @@ -30,6 +31,7 @@ def josephus(items: list[Any], skip: int) -> Generator[Any, None, None]: Examples: >>> list(josephus([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)) [3, 6, 9, 4, 8, 5, 2, 7, 1] + """ skip = skip - 1 index = 0 diff --git a/algorithms/array/limit.py b/algorithms/array/limit.py index ae8ed730c..caa2014f9 100644 --- a/algorithms/array/limit.py +++ b/algorithms/array/limit.py @@ -1,5 +1,4 @@ -""" -Limit Array Values +"""Limit Array Values. Filter an array to include only elements within a specified minimum and maximum range (inclusive). @@ -32,6 +31,7 @@ def limit( Examples: >>> limit([1, 2, 3, 4, 5], 2, 4) [2, 3, 4] + """ if len(array) == 0: return array diff --git a/algorithms/array/longest_non_repeat.py b/algorithms/array/longest_non_repeat.py index 9f24cdfc7..96e39e9fa 100644 --- a/algorithms/array/longest_non_repeat.py +++ b/algorithms/array/longest_non_repeat.py @@ -1,5 +1,4 @@ -""" -Longest Substring Without Repeating Characters +"""Longest Substring Without Repeating Characters. Given a string, find the length of the longest substring without repeating characters. Multiple algorithm variants are provided. @@ -26,6 +25,7 @@ def longest_non_repeat_v1(string: str) -> int: Examples: >>> longest_non_repeat_v1("abcabcbb") 3 + """ if string is None: return 0 @@ -52,6 +52,7 @@ def longest_non_repeat_v2(string: str) -> int: Examples: >>> longest_non_repeat_v2("abcabcbb") 3 + """ if string is None: return 0 @@ -78,6 +79,7 @@ def get_longest_non_repeat_v1(string: str) -> tuple[int, str]: Examples: >>> get_longest_non_repeat_v1("abcabcbb") (3, 'abc') + """ if string is None: return 0, "" @@ -107,6 +109,7 @@ def get_longest_non_repeat_v2(string: str) -> tuple[int, str]: Examples: >>> get_longest_non_repeat_v2("abcabcbb") (3, 'abc') + """ if string is None: return 0, "" @@ -116,10 +119,9 @@ def get_longest_non_repeat_v2(string: str) -> tuple[int, str]: for index, char in enumerate(string): if char in used_char and start <= used_char[char]: start = used_char[char] + 1 - else: - if index - start + 1 > max_length: - max_length = index - start + 1 - substring = string[start : index + 1] + elif index - start + 1 > max_length: + max_length = index - start + 1 + substring = string[start : index + 1] used_char[char] = index return max_length, substring @@ -136,6 +138,7 @@ def get_longest_non_repeat_v3(string: str) -> tuple[int, str]: Examples: >>> get_longest_non_repeat_v3("abcabcbb") (3, 'abc') + """ longest_substring = "" seen = set() diff --git a/algorithms/array/max_ones_index.py b/algorithms/array/max_ones_index.py index 8bf599398..088a5b3fa 100644 --- a/algorithms/array/max_ones_index.py +++ b/algorithms/array/max_ones_index.py @@ -1,5 +1,4 @@ -""" -Max Ones Index +"""Max Ones Index. Find the index of the 0 that, when replaced with 1, produces the longest continuous sequence of 1s in a binary array. Returns -1 if no 0 exists. @@ -26,6 +25,7 @@ def max_ones_index(array: list[int]) -> int: Examples: >>> max_ones_index([1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1]) 3 + """ length = len(array) max_count = 0 diff --git a/algorithms/array/merge_intervals.py b/algorithms/array/merge_intervals.py index bebe7424f..e8b6f9be9 100644 --- a/algorithms/array/merge_intervals.py +++ b/algorithms/array/merge_intervals.py @@ -1,5 +1,4 @@ -""" -Merge Intervals +"""Merge Intervals. Given a collection of intervals, merge all overlapping intervals into a consolidated set. @@ -20,6 +19,7 @@ class Interval: Args: start: Lower bound of the interval. end: Upper bound of the interval. + """ def __init__(self, start: int = 0, end: int = 0) -> None: @@ -53,6 +53,7 @@ def as_list(self) -> list[int]: Returns: List of integers in the interval range. + """ return list(self) @@ -69,6 +70,7 @@ def merge(intervals: list[Interval]) -> list[Interval]: Examples: >>> Interval.merge([Interval(1, 3), Interval(2, 6)]) [Interval (1, 6)] + """ out = [] for interval in sorted(intervals, key=lambda i: i.start): @@ -91,6 +93,7 @@ def print_intervals(intervals: list[Interval]) -> str: Examples: >>> Interval.print_intervals([Interval(1, 3)]) 'Interval (1, 3)' + """ result = [] for interval in intervals: @@ -110,6 +113,7 @@ def merge_intervals(intervals: list[list[int]]) -> list[list[int]] | None: Examples: >>> merge_intervals([[1, 3], [2, 6], [8, 10]]) [[1, 6], [8, 10]] + """ if intervals is None: return None diff --git a/algorithms/array/missing_ranges.py b/algorithms/array/missing_ranges.py index c41492b06..d5b139919 100644 --- a/algorithms/array/missing_ranges.py +++ b/algorithms/array/missing_ranges.py @@ -1,5 +1,4 @@ -""" -Missing Ranges +"""Missing Ranges. Find the ranges of numbers that are missing between a given low and high bound, given a sorted array of integers. @@ -28,6 +27,7 @@ def missing_ranges(array: list[int], low: int, high: int) -> list[tuple[int, int Examples: >>> missing_ranges([3, 5], 1, 10) [(1, 2), (4, 4), (6, 10)] + """ result = [] start = low diff --git a/algorithms/array/move_zeros.py b/algorithms/array/move_zeros.py index 6ad36d794..e95ed5f93 100644 --- a/algorithms/array/move_zeros.py +++ b/algorithms/array/move_zeros.py @@ -1,5 +1,4 @@ -""" -Move Zeros +"""Move Zeros. Move all zeros in an array to the end while preserving the relative order of the non-zero (and non-integer-zero) elements. @@ -30,6 +29,7 @@ def move_zeros(array: list[Any]) -> list[Any]: Examples: >>> move_zeros([False, 1, 0, 1, 2, 0, 1, 3, "a"]) [False, 1, 1, 2, 1, 3, 'a', 0, 0] + """ result = [] zeros = 0 diff --git a/algorithms/array/n_sum.py b/algorithms/array/n_sum.py index 217a96e44..139bff779 100644 --- a/algorithms/array/n_sum.py +++ b/algorithms/array/n_sum.py @@ -1,5 +1,4 @@ -""" -N-Sum +"""N-Sum. Given an array of integers, find all unique n-tuples that sum to a target value. Supports custom sum, comparison, and equality closures for advanced @@ -14,8 +13,10 @@ from __future__ import annotations -from collections.abc import Callable -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Callable def n_sum( @@ -41,6 +42,7 @@ def n_sum( Examples: >>> n_sum(3, [-1, 0, 1, 2, -1, -4], 0) [[-1, -1, 2], [-1, 0, 1]] + """ def _sum_closure_default(a: Any, b: Any) -> Any: @@ -49,10 +51,9 @@ def _sum_closure_default(a: Any, b: Any) -> Any: def _compare_closure_default(num: Any, target: Any) -> int: if num < target: return -1 - elif num > target: + if num > target: return 1 - else: - return 0 + return 0 def _same_closure_default(a: Any, b: Any) -> bool: return a == b @@ -100,7 +101,7 @@ def _two_sum(nums: list[Any], target: Any) -> list[list[Any]]: return results def _append_elem_to_each_list( - elem: Any, container: list[list[Any]] + elem: Any, container: list[list[Any]], ) -> list[list[Any]]: results = [] for elems in container: diff --git a/algorithms/array/plus_one.py b/algorithms/array/plus_one.py index 621a95924..b0f553fb9 100644 --- a/algorithms/array/plus_one.py +++ b/algorithms/array/plus_one.py @@ -1,5 +1,4 @@ -""" -Plus One +"""Plus One. Given a non-negative number represented as an array of digits (big-endian), add one to the number and return the resulting digit array. @@ -26,6 +25,7 @@ def plus_one_v1(digits: list[int]) -> list[int]: Examples: >>> plus_one_v1([1, 2, 9]) [1, 3, 0] + """ digits[-1] = digits[-1] + 1 result = [] @@ -55,6 +55,7 @@ def plus_one_v2(digits: list[int]) -> list[int]: Examples: >>> plus_one_v2([1, 2, 9]) [1, 3, 0] + """ length = len(digits) for index in range(length - 1, -1, -1): @@ -78,9 +79,10 @@ def plus_one_v3(num_arr: list[int]) -> list[int]: Examples: >>> plus_one_v3([1, 2, 9]) [1, 3, 0] + """ for idx in reversed(list(enumerate(num_arr))): num_arr[idx[0]] = (num_arr[idx[0]] + 1) % 10 if num_arr[idx[0]]: return num_arr - return [1] + num_arr + return [1, *num_arr] diff --git a/algorithms/array/remove_duplicates.py b/algorithms/array/remove_duplicates.py index 33eff3334..95d06209b 100644 --- a/algorithms/array/remove_duplicates.py +++ b/algorithms/array/remove_duplicates.py @@ -1,5 +1,4 @@ -""" -Remove Duplicates +"""Remove Duplicates. Remove duplicate elements from an array while preserving the original order. Handles both hashable and unhashable items. @@ -32,6 +31,7 @@ def remove_duplicates(array: list[Any]) -> list[Any]: Examples: >>> remove_duplicates([1, 1, 2, 2, 3]) [1, 2, 3] + """ seen = set() unique_array = [] @@ -41,8 +41,7 @@ def remove_duplicates(array: list[Any]) -> list[Any]: if item not in seen: seen.add(item) unique_array.append(item) - else: - if item not in unique_array: - unique_array.append(item) + elif item not in unique_array: + unique_array.append(item) return unique_array diff --git a/algorithms/array/rotate.py b/algorithms/array/rotate.py index 6eb962535..39e34d5eb 100644 --- a/algorithms/array/rotate.py +++ b/algorithms/array/rotate.py @@ -1,5 +1,4 @@ -""" -Rotate Array +"""Rotate Array. Rotate an array of n elements to the right by k steps. Three algorithm variants are provided with different time complexities. @@ -28,6 +27,7 @@ def rotate_v1(array: list[int], k: int) -> list[int]: Examples: >>> rotate_v1([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] + """ array = array[:] length = len(array) @@ -52,6 +52,7 @@ def rotate_v2(array: list[int], k: int) -> list[int]: Examples: >>> rotate_v2([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] + """ array = array[:] @@ -82,6 +83,7 @@ def rotate_v3(array: list[int] | None, k: int) -> list[int] | None: Examples: >>> rotate_v3([1, 2, 3, 4, 5, 6, 7], 3) [5, 6, 7, 1, 2, 3, 4] + """ if array is None: return None diff --git a/algorithms/array/summarize_ranges.py b/algorithms/array/summarize_ranges.py index 392f13a50..9c4dc0251 100644 --- a/algorithms/array/summarize_ranges.py +++ b/algorithms/array/summarize_ranges.py @@ -1,5 +1,4 @@ -""" -Summarize Ranges +"""Summarize Ranges. Given a sorted integer array without duplicates, return the summary of its ranges as a list of (start, end) tuples. @@ -26,6 +25,7 @@ def summarize_ranges(array: list[int]) -> list[tuple[int, ...]]: Examples: >>> summarize_ranges([0, 1, 2, 4, 5, 7]) [(0, 2), (4, 5), (7, 7)] + """ result = [] if len(array) == 0: diff --git a/algorithms/array/three_sum.py b/algorithms/array/three_sum.py index 3893e0383..ec071b83a 100644 --- a/algorithms/array/three_sum.py +++ b/algorithms/array/three_sum.py @@ -1,5 +1,4 @@ -""" -Three Sum +"""Three Sum. Given an array of integers, find all unique triplets that sum to zero using the two-pointer technique. @@ -26,6 +25,7 @@ def three_sum(array: list[int]) -> set[tuple[int, int, int]]: Examples: >>> three_sum([-1, 0, 1, 2, -1, -4]) == {(-1, 0, 1), (-1, -1, 2)} True + """ result = set() array.sort() diff --git a/algorithms/array/top_1.py b/algorithms/array/top_1.py index b75a7ab32..94391922f 100644 --- a/algorithms/array/top_1.py +++ b/algorithms/array/top_1.py @@ -1,5 +1,4 @@ -""" -Top 1 (Mode) +"""Top 1 (Mode). Find the most frequently occurring value(s) in an array. When multiple values share the highest frequency, all are returned. @@ -28,6 +27,7 @@ def top_1(array: list[Any]) -> list[Any]: Examples: >>> top_1([1, 1, 2, 2, 3]) [1, 2] + """ frequency = {} for element in array: diff --git a/algorithms/array/trimmean.py b/algorithms/array/trimmean.py index 03d18ae2d..3e3cf524a 100644 --- a/algorithms/array/trimmean.py +++ b/algorithms/array/trimmean.py @@ -1,5 +1,4 @@ -""" -Trimmed Mean +"""Trimmed Mean. Compute the mean of an array after discarding a given percentage of the highest and lowest values. Useful for robust averaging in scoring systems. @@ -31,6 +30,7 @@ def trimmean(array: list[float], percentage: float) -> float: Examples: >>> trimmean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 20) 5.5 + """ ratio = percentage / 200 array.sort() diff --git a/algorithms/array/two_sum.py b/algorithms/array/two_sum.py index 0dee2aaf1..fefc5f2e7 100644 --- a/algorithms/array/two_sum.py +++ b/algorithms/array/two_sum.py @@ -1,5 +1,4 @@ -""" -Two Sum +"""Two Sum. Given an array of integers and a target sum, return the indices of the two numbers that add up to the target. @@ -27,11 +26,11 @@ def two_sum(array: list[int], target: int) -> tuple[int, int] | None: Examples: >>> two_sum([2, 7, 11, 15], 9) (0, 1) + """ seen = {} for index, num in enumerate(array): if num in seen: return seen[num], index - else: - seen[target - num] = index + seen[target - num] = index return None diff --git a/algorithms/backtracking/__init__.py b/algorithms/backtracking/__init__.py index be4c274a6..03f3051f8 100644 --- a/algorithms/backtracking/__init__.py +++ b/algorithms/backtracking/__init__.py @@ -25,15 +25,14 @@ "add_operators", "anagram", "array_sum_combinations", - "unique_array_sum_combinations", "combination_sum", - "get_factors", - "recursive_get_factors", "find_words", "generate_abbreviations", "generate_parenthesis_v1", "generate_parenthesis_v2", + "get_factors", "letter_combinations", + "minimax", "palindromic_substrings", "palindromic_substrings_iter", "pattern_match", @@ -41,8 +40,9 @@ "permute_iter", "permute_recursive", "permute_unique", + "recursive_get_factors", "subsets", - "subsets_v2", "subsets_unique", - "minimax", + "subsets_v2", + "unique_array_sum_combinations", ] diff --git a/algorithms/backtracking/add_operators.py b/algorithms/backtracking/add_operators.py index 67373ac95..6c246d56c 100644 --- a/algorithms/backtracking/add_operators.py +++ b/algorithms/backtracking/add_operators.py @@ -1,5 +1,4 @@ -""" -Expression Add Operators +"""Expression Add Operators. Given a string of digits and a target value, return all possibilities to insert binary operators (+, -, *) between the digits so they evaluate to @@ -28,6 +27,7 @@ def add_operators(digits: str, target: int) -> list[str]: Examples: >>> add_operators("123", 6) ['1+2+3', '1*2*3'] + """ result: list[str] = [] if not digits: diff --git a/algorithms/backtracking/anagram.py b/algorithms/backtracking/anagram.py index f1dfc6311..6b23e21ff 100644 --- a/algorithms/backtracking/anagram.py +++ b/algorithms/backtracking/anagram.py @@ -1,5 +1,4 @@ -""" -Anagram Checker +"""Anagram Checker. Given two strings, determine if they are anagrams of each other (i.e. one can be rearranged to form the other). @@ -29,6 +28,7 @@ def anagram(first: str, second: str) -> bool: True >>> anagram('apple', 'cherry') False + """ count_first = [0] * 26 count_second = [0] * 26 diff --git a/algorithms/backtracking/array_sum_combinations.py b/algorithms/backtracking/array_sum_combinations.py index 9b83f4761..c8469bcee 100644 --- a/algorithms/backtracking/array_sum_combinations.py +++ b/algorithms/backtracking/array_sum_combinations.py @@ -1,5 +1,4 @@ -""" -Array Sum Combinations +"""Array Sum Combinations. Given three arrays and a target sum, find all three-element combinations (one element from each array) that add up to the target. @@ -39,6 +38,7 @@ def array_sum_combinations( Examples: >>> array_sum_combinations([1], [2], [3], 6) [[1, 2, 3]] + """ arrays = [array_a, array_b, array_c] @@ -98,13 +98,13 @@ def unique_array_sum_combinations( Examples: >>> sorted(unique_array_sum_combinations([1, 2], [2, 3], [3, 4], 6)) [(1, 2, 3)] + """ def _check_sum(expected: int, *nums: int) -> tuple[bool, tuple[int, ...]]: if sum(nums) == expected: return (True, nums) - else: - return (False, nums) + return (False, nums) product = itertools.product(array_a, array_b, array_c) func = partial(_check_sum, target) diff --git a/algorithms/backtracking/combination_sum.py b/algorithms/backtracking/combination_sum.py index 7eb1435d4..ba6ada5e3 100644 --- a/algorithms/backtracking/combination_sum.py +++ b/algorithms/backtracking/combination_sum.py @@ -1,5 +1,4 @@ -""" -Combination Sum +"""Combination Sum. Given a set of candidate numbers (without duplicates) and a target number, find all unique combinations where the candidate numbers sum to the target. @@ -28,6 +27,7 @@ def combination_sum(candidates: list[int], target: int) -> list[list[int]]: Examples: >>> combination_sum([2, 3, 6, 7], 7) [[2, 2, 3], [7]] + """ result: list[list[int]] = [] candidates.sort() @@ -49,4 +49,4 @@ def _dfs( result.append(path) return for i in range(index, len(nums)): - _dfs(nums, target - nums[i], i, path + [nums[i]], result) + _dfs(nums, target - nums[i], i, [*path, nums[i]], result) diff --git a/algorithms/backtracking/factor_combinations.py b/algorithms/backtracking/factor_combinations.py index a38120e95..54eb08904 100644 --- a/algorithms/backtracking/factor_combinations.py +++ b/algorithms/backtracking/factor_combinations.py @@ -1,5 +1,4 @@ -""" -Factor Combinations +"""Factor Combinations. Given an integer n, return all possible combinations of its factors. Factors should be greater than 1 and less than n. @@ -26,6 +25,7 @@ def get_factors(number: int) -> list[list[int]]: Examples: >>> get_factors(12) [[2, 6], [3, 4], [2, 2, 3]] + """ todo: list[tuple[int, int, list[int]]] = [(number, 2, [])] combinations: list[list[int]] = [] @@ -33,8 +33,8 @@ def get_factors(number: int) -> list[list[int]]: remaining, divisor, partial = todo.pop() while divisor * divisor <= remaining: if remaining % divisor == 0: - combinations.append(partial + [divisor, remaining // divisor]) - todo.append((remaining // divisor, divisor, partial + [divisor])) + combinations.append([*partial, divisor, remaining // divisor]) + todo.append((remaining // divisor, divisor, [*partial, divisor])) divisor += 1 return combinations @@ -51,6 +51,7 @@ def recursive_get_factors(number: int) -> list[list[int]]: Examples: >>> recursive_get_factors(12) [[2, 6], [2, 2, 3], [3, 4]] + """ def _factor( @@ -61,9 +62,9 @@ def _factor( ) -> list[list[int]]: while divisor * divisor <= remaining: if remaining % divisor == 0: - combinations.append(partial + [divisor, remaining // divisor]) + combinations.append([*partial, divisor, remaining // divisor]) _factor( - remaining // divisor, divisor, partial + [divisor], combinations + remaining // divisor, divisor, [*partial, divisor], combinations, ) divisor += 1 return combinations diff --git a/algorithms/backtracking/find_words.py b/algorithms/backtracking/find_words.py index d1a87673a..85672cb10 100644 --- a/algorithms/backtracking/find_words.py +++ b/algorithms/backtracking/find_words.py @@ -1,5 +1,4 @@ -""" -Word Search II +"""Word Search II. Given a board of characters and a list of words, find all words that can be constructed from adjacent cells (horizontally or vertically). Each cell @@ -32,6 +31,7 @@ def find_words(board: list[list[str]], words: list[str]) -> list[str]: >>> board = [['o','a','a','n'], ['e','t','a','e']] >>> sorted(find_words(board, ['eat', 'oath'])) ['eat'] + """ trie: dict = {} for word in words: @@ -72,15 +72,15 @@ def _backtrack( used[row][col] = True next_char = board[row][col] _backtrack( - board, row + 1, col, trie[next_char], prefix + next_char, used, found + board, row + 1, col, trie[next_char], prefix + next_char, used, found, ) _backtrack( - board, row, col + 1, trie[next_char], prefix + next_char, used, found + board, row, col + 1, trie[next_char], prefix + next_char, used, found, ) _backtrack( - board, row - 1, col, trie[next_char], prefix + next_char, used, found + board, row - 1, col, trie[next_char], prefix + next_char, used, found, ) _backtrack( - board, row, col - 1, trie[next_char], prefix + next_char, used, found + board, row, col - 1, trie[next_char], prefix + next_char, used, found, ) used[row][col] = False diff --git a/algorithms/backtracking/generate_abbreviations.py b/algorithms/backtracking/generate_abbreviations.py index d96d17af5..0f1752e67 100644 --- a/algorithms/backtracking/generate_abbreviations.py +++ b/algorithms/backtracking/generate_abbreviations.py @@ -1,5 +1,4 @@ -""" -Generalized Abbreviations +"""Generalized Abbreviations. Given a word, return all possible generalized abbreviations. Each abbreviation replaces contiguous substrings with their lengths. @@ -26,6 +25,7 @@ def generate_abbreviations(word: str) -> list[str]: Examples: >>> sorted(generate_abbreviations("ab")) ['1b', '2', 'a1', 'ab'] + """ result: list[str] = [] _backtrack(result, word, 0, 0, "") diff --git a/algorithms/backtracking/generate_parenthesis.py b/algorithms/backtracking/generate_parenthesis.py index 00779468a..8cd8dc53a 100644 --- a/algorithms/backtracking/generate_parenthesis.py +++ b/algorithms/backtracking/generate_parenthesis.py @@ -1,5 +1,4 @@ -""" -Generate Parentheses +"""Generate Parentheses. Given n pairs of parentheses, generate all combinations of well-formed parentheses. @@ -29,6 +28,7 @@ def generate_parenthesis_v1(count: int) -> list[str]: Examples: >>> generate_parenthesis_v1(2) ['()()', '(())'] + """ result: list[str] = [] _add_pair_v1(result, "", count, 0) @@ -66,6 +66,7 @@ def generate_parenthesis_v2(count: int) -> list[str]: Examples: >>> generate_parenthesis_v2(2) ['(())', '()()'] + """ result: list[str] = [] _add_pair_v2(result, "", count, count) diff --git a/algorithms/backtracking/letter_combination.py b/algorithms/backtracking/letter_combination.py index 8e4ba30d6..a9ea9b26f 100644 --- a/algorithms/backtracking/letter_combination.py +++ b/algorithms/backtracking/letter_combination.py @@ -1,5 +1,4 @@ -""" -Letter Combinations of a Phone Number +"""Letter Combinations of a Phone Number. Given a digit string, return all possible letter combinations that the number could represent using a telephone keypad mapping. @@ -26,6 +25,7 @@ def letter_combinations(digits: str) -> list[str]: Examples: >>> letter_combinations("23") ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'] + """ if digits == "": return [] diff --git a/algorithms/backtracking/minimax.py b/algorithms/backtracking/minimax.py index 029f8fdaf..aaac4d3c4 100644 --- a/algorithms/backtracking/minimax.py +++ b/algorithms/backtracking/minimax.py @@ -45,16 +45,15 @@ def minimax( minimax(depth - 1, False, scores[mid:], alpha, beta), ) return value - else: - value = math.inf + value = math.inf + value = min( + value, + minimax(depth - 1, True, scores[:mid], alpha, beta), + ) + beta = min(beta, value) + if alpha < beta: value = min( value, - minimax(depth - 1, True, scores[:mid], alpha, beta), + minimax(depth - 1, True, scores[mid:], alpha, beta), ) - beta = min(beta, value) - if alpha < beta: - value = min( - value, - minimax(depth - 1, True, scores[mid:], alpha, beta), - ) - return value + return value diff --git a/algorithms/backtracking/palindrome_partitioning.py b/algorithms/backtracking/palindrome_partitioning.py index 879b18bbb..b5952106d 100644 --- a/algorithms/backtracking/palindrome_partitioning.py +++ b/algorithms/backtracking/palindrome_partitioning.py @@ -1,5 +1,4 @@ -""" -Palindrome Partitioning +"""Palindrome Partitioning. Given a string, find all ways to partition it into palindromic substrings. There is always at least one way since single characters are palindromes. @@ -13,7 +12,10 @@ from __future__ import annotations -from collections.abc import Generator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Generator def palindromic_substrings(text: str) -> list[list[str]]: @@ -28,6 +30,7 @@ def palindromic_substrings(text: str) -> list[list[str]]: Examples: >>> palindromic_substrings("abc") [['a', 'b', 'c']] + """ if not text: return [[]] @@ -36,7 +39,7 @@ def palindromic_substrings(text: str) -> list[list[str]]: substring = text[:i] if substring == substring[::-1]: for rest in palindromic_substrings(text[i:]): - results.append([substring] + rest) + results.append([substring, *rest]) return results @@ -54,6 +57,7 @@ def palindromic_substrings_iter(text: str) -> Generator[list[str], None, None]: Examples: >>> list(palindromic_substrings_iter("abc")) [['a', 'b', 'c']] + """ if not text: yield [] @@ -62,4 +66,4 @@ def palindromic_substrings_iter(text: str) -> Generator[list[str], None, None]: substring = text[:i] if substring == substring[::-1]: for rest in palindromic_substrings_iter(text[i:]): - yield [substring] + rest + yield [substring, *rest] diff --git a/algorithms/backtracking/pattern_match.py b/algorithms/backtracking/pattern_match.py index 493d1cb8c..38d3e97b5 100644 --- a/algorithms/backtracking/pattern_match.py +++ b/algorithms/backtracking/pattern_match.py @@ -1,5 +1,4 @@ -""" -Pattern Matching +"""Pattern Matching. Given a pattern and a string, determine if the string follows the same pattern. A full match means a bijection between each letter in the pattern @@ -30,6 +29,7 @@ def pattern_match(pattern: str, string: str) -> bool: True >>> pattern_match("aabb", "xyzabcxzyabc") False + """ return _backtrack(pattern, string, {}) diff --git a/algorithms/backtracking/permute.py b/algorithms/backtracking/permute.py index b7738c62a..400c6f337 100644 --- a/algorithms/backtracking/permute.py +++ b/algorithms/backtracking/permute.py @@ -1,5 +1,4 @@ -""" -Permutations +"""Permutations. Given a collection of distinct elements, return all possible permutations. @@ -12,7 +11,10 @@ from __future__ import annotations -from collections.abc import Generator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Generator def permute(elements: list | str) -> list: @@ -27,6 +29,7 @@ def permute(elements: list | str) -> list: Examples: >>> permute([1, 2, 3]) [[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 3, 2], [3, 1, 2], [3, 2, 1]] + """ if len(elements) <= 1: return [elements] @@ -49,6 +52,7 @@ def permute_iter(elements: list | str) -> Generator: Examples: >>> list(permute_iter([1, 2])) [[1, 2], [2, 1]] + """ if len(elements) <= 1: yield elements @@ -70,6 +74,7 @@ def permute_recursive(nums: list[int]) -> list[list[int]]: Examples: >>> sorted(permute_recursive([1, 2])) [[1, 2], [2, 1]] + """ result: list[list[int]] = [] _dfs(result, nums, []) @@ -85,4 +90,4 @@ def _dfs( if not nums: result.append(path) for i in range(len(nums)): - _dfs(result, nums[:i] + nums[i + 1 :], path + [nums[i]]) + _dfs(result, nums[:i] + nums[i + 1 :], [*path, nums[i]]) diff --git a/algorithms/backtracking/permute_unique.py b/algorithms/backtracking/permute_unique.py index 46797fd91..cf68729ab 100644 --- a/algorithms/backtracking/permute_unique.py +++ b/algorithms/backtracking/permute_unique.py @@ -1,5 +1,4 @@ -""" -Unique Permutations +"""Unique Permutations. Given a collection of numbers that might contain duplicates, return all possible unique permutations. @@ -26,13 +25,14 @@ def permute_unique(nums: list[int]) -> list[list[int]]: Examples: >>> sorted(permute_unique([1, 1, 2])) [[1, 1, 2], [1, 2, 1], [2, 1, 1]] + """ permutations: list[list[int]] = [[]] for number in nums: new_permutations: list[list[int]] = [] for existing in permutations: for i in range(len(existing) + 1): - new_permutations.append(existing[:i] + [number] + existing[i:]) + new_permutations.append([*existing[:i], number, *existing[i:]]) if i < len(existing) and existing[i] == number: break permutations = new_permutations diff --git a/algorithms/backtracking/subsets.py b/algorithms/backtracking/subsets.py index ecf80ea97..452b0ad37 100644 --- a/algorithms/backtracking/subsets.py +++ b/algorithms/backtracking/subsets.py @@ -1,5 +1,4 @@ -""" -Subsets +"""Subsets. Given a set of distinct integers, return all possible subsets (the power set). The solution set must not contain duplicate subsets. @@ -26,6 +25,7 @@ def subsets(nums: list[int]) -> list[list[int]]: Examples: >>> sorted(subsets([1, 2]), key=lambda item: (len(item), item)) [[], [1], [2], [1, 2]] + """ result: list[list[int]] = [] _backtrack(result, nums, [], 0) @@ -63,8 +63,9 @@ def subsets_v2(nums: list[int]) -> list[list[int]]: Examples: >>> sorted(subsets_v2([1, 2]), key=lambda item: (len(item), item)) [[], [1], [2], [1, 2]] + """ result: list[list[int]] = [[]] for number in sorted(nums): - result += [item + [number] for item in result] + result += [[*item, number] for item in result] return result diff --git a/algorithms/backtracking/subsets_unique.py b/algorithms/backtracking/subsets_unique.py index 592aa0109..a0dafaf4c 100644 --- a/algorithms/backtracking/subsets_unique.py +++ b/algorithms/backtracking/subsets_unique.py @@ -1,5 +1,4 @@ -""" -Unique Subsets +"""Unique Subsets. Given a collection of integers that might contain duplicates, return all possible unique subsets (the power set without duplicates). @@ -26,6 +25,7 @@ def subsets_unique(nums: list[int]) -> list[tuple[int, ...]]: Examples: >>> sorted(subsets_unique([1, 2, 2])) [(), (1,), (1, 2), (1, 2, 2), (2,), (2, 2)] + """ found: set[tuple[int, ...]] = set() _backtrack(found, nums, [], 0) diff --git a/algorithms/bit_manipulation/__init__.py b/algorithms/bit_manipulation/__init__.py index 9a999fcf1..8b5abe4d8 100644 --- a/algorithms/bit_manipulation/__init__.py +++ b/algorithms/bit_manipulation/__init__.py @@ -38,6 +38,8 @@ "find_missing_number2", "flip_bit_longest_seq", "get_bit", + "gray_code", + "gray_to_binary", "has_alternative_bit", "has_alternative_bit_fast", "insert_mult_bits", @@ -54,6 +56,4 @@ "subsets", "swap_pair", "update_bit", - "gray_code", - "gray_to_binary", ] diff --git a/algorithms/bit_manipulation/add_bitwise_operator.py b/algorithms/bit_manipulation/add_bitwise_operator.py index 44d47907f..3cf291855 100644 --- a/algorithms/bit_manipulation/add_bitwise_operator.py +++ b/algorithms/bit_manipulation/add_bitwise_operator.py @@ -1,5 +1,4 @@ -""" -Add Bitwise Operator +"""Add Bitwise Operator. Add two positive integers without using the '+' operator, using only bitwise operations (AND, XOR, shift). @@ -29,9 +28,11 @@ def add_bitwise_operator(first: int, second: int) -> int: 5 >>> add_bitwise_operator(0, 0) 0 + """ if first < 0 or second < 0: - raise ValueError("The provided values must be non-negative!") + msg = "The provided values must be non-negative!" + raise ValueError(msg) while second: carry = first & second first = first ^ second diff --git a/algorithms/bit_manipulation/binary_gap.py b/algorithms/bit_manipulation/binary_gap.py index 328a99b3f..47367f261 100644 --- a/algorithms/bit_manipulation/binary_gap.py +++ b/algorithms/bit_manipulation/binary_gap.py @@ -1,5 +1,4 @@ -""" -Binary Gap +"""Binary Gap. Given a positive integer N, find and return the longest distance between two consecutive 1-bits in the binary representation of N. If there are not two @@ -30,6 +29,7 @@ def binary_gap(number: int) -> int: 2 >>> binary_gap(8) 0 + """ last_one_position = None longest_gap = 0 diff --git a/algorithms/bit_manipulation/bit_operation.py b/algorithms/bit_manipulation/bit_operation.py index 7a2da568b..76f6d6746 100644 --- a/algorithms/bit_manipulation/bit_operation.py +++ b/algorithms/bit_manipulation/bit_operation.py @@ -1,5 +1,4 @@ -""" -Fundamental Bit Operations +"""Fundamental Bit Operations. Basic bit manipulation operations: get, set, clear, and update individual bits at a specific position in an integer. @@ -32,6 +31,7 @@ def get_bit(number: int, position: int) -> int: 1 >>> get_bit(22, 3) 0 + """ return (number & (1 << position)) != 0 @@ -52,6 +52,7 @@ def set_bit(number: int, position: int) -> int: Examples: >>> set_bit(22, 3) 30 + """ return number | (1 << position) @@ -72,6 +73,7 @@ def clear_bit(number: int, position: int) -> int: Examples: >>> clear_bit(22, 2) 18 + """ mask = ~(1 << position) return number & mask @@ -96,6 +98,7 @@ def update_bit(number: int, position: int, bit: int) -> int: 30 >>> update_bit(22, 2, 0) 18 + """ mask = ~(1 << position) return (number & mask) | (bit << position) diff --git a/algorithms/bit_manipulation/bytes_int_conversion.py b/algorithms/bit_manipulation/bytes_int_conversion.py index 24c354f98..5c704597e 100644 --- a/algorithms/bit_manipulation/bytes_int_conversion.py +++ b/algorithms/bit_manipulation/bytes_int_conversion.py @@ -1,5 +1,4 @@ -""" -Bytes-Integer Conversion +"""Bytes-Integer Conversion. Convert between Python integers and raw byte sequences in both big-endian and little-endian byte orders. @@ -17,7 +16,7 @@ def int_to_bytes_big_endian(number: int) -> bytes: - """Convert a non-negative integer to bytes in big-endian order. + r"""Convert a non-negative integer to bytes in big-endian order. Args: number: A non-negative integer to convert. @@ -28,6 +27,7 @@ def int_to_bytes_big_endian(number: int) -> bytes: Examples: >>> int_to_bytes_big_endian(17) b'\\x11' + """ byte_buffer: deque[int] = deque() while number > 0: @@ -37,7 +37,7 @@ def int_to_bytes_big_endian(number: int) -> bytes: def int_to_bytes_little_endian(number: int) -> bytes: - """Convert a non-negative integer to bytes in little-endian order. + r"""Convert a non-negative integer to bytes in little-endian order. Args: number: A non-negative integer to convert. @@ -48,6 +48,7 @@ def int_to_bytes_little_endian(number: int) -> bytes: Examples: >>> int_to_bytes_little_endian(17) b'\\x11' + """ byte_buffer: list[int] = [] while number > 0: @@ -57,7 +58,7 @@ def int_to_bytes_little_endian(number: int) -> bytes: def bytes_big_endian_to_int(byte_string: bytes) -> int: - """Convert a big-endian byte sequence to an integer. + r"""Convert a big-endian byte sequence to an integer. Args: byte_string: Bytes with the most significant byte first. @@ -68,6 +69,7 @@ def bytes_big_endian_to_int(byte_string: bytes) -> int: Examples: >>> bytes_big_endian_to_int(b'\\x11') 17 + """ number = 0 for byte in byte_string: @@ -77,7 +79,7 @@ def bytes_big_endian_to_int(byte_string: bytes) -> int: def bytes_little_endian_to_int(byte_string: bytes) -> int: - """Convert a little-endian byte sequence to an integer. + r"""Convert a little-endian byte sequence to an integer. Args: byte_string: Bytes with the least significant byte first. @@ -88,6 +90,7 @@ def bytes_little_endian_to_int(byte_string: bytes) -> int: Examples: >>> bytes_little_endian_to_int(b'\\x11') 17 + """ number = 0 exponent = 0 diff --git a/algorithms/bit_manipulation/count_flips_to_convert.py b/algorithms/bit_manipulation/count_flips_to_convert.py index bab25d8cf..b52ea2ad4 100644 --- a/algorithms/bit_manipulation/count_flips_to_convert.py +++ b/algorithms/bit_manipulation/count_flips_to_convert.py @@ -1,5 +1,4 @@ -""" -Count Flips to Convert +"""Count Flips to Convert. Determine the minimal number of bits you would need to flip to convert integer A to integer B. Uses XOR to find differing bits and Brian @@ -30,6 +29,7 @@ def count_flips_to_convert(first: int, second: int) -> int: 2 >>> count_flips_to_convert(34, 34) 0 + """ diff = first ^ second count = 0 diff --git a/algorithms/bit_manipulation/count_ones.py b/algorithms/bit_manipulation/count_ones.py index b474ac52a..0c6d90c30 100644 --- a/algorithms/bit_manipulation/count_ones.py +++ b/algorithms/bit_manipulation/count_ones.py @@ -1,5 +1,4 @@ -""" -Count Ones (Hamming Weight) +"""Count Ones (Hamming Weight). Count the number of 1-bits in the binary representation of an unsigned integer using Brian Kernighan's algorithm. @@ -28,6 +27,7 @@ def count_ones_recur(number: int) -> int: 1 >>> count_ones_recur(63) 6 + """ if not number: return 0 @@ -48,6 +48,7 @@ def count_ones_iter(number: int) -> int: 1 >>> count_ones_iter(63) 6 + """ count = 0 while number: diff --git a/algorithms/bit_manipulation/find_difference.py b/algorithms/bit_manipulation/find_difference.py index 541bb0020..66d81c1ad 100644 --- a/algorithms/bit_manipulation/find_difference.py +++ b/algorithms/bit_manipulation/find_difference.py @@ -1,5 +1,4 @@ -""" -Find the Difference +"""Find the Difference. Given two strings where the second is generated by shuffling the first and adding one extra letter, find the added letter using XOR. @@ -30,6 +29,7 @@ def find_difference(original: str, shuffled: str) -> str: Examples: >>> find_difference("abcd", "abecd") 'e' + """ xor_result = 0 for character in original + shuffled: diff --git a/algorithms/bit_manipulation/find_missing_number.py b/algorithms/bit_manipulation/find_missing_number.py index bc57c876f..4cc5297e1 100644 --- a/algorithms/bit_manipulation/find_missing_number.py +++ b/algorithms/bit_manipulation/find_missing_number.py @@ -1,5 +1,4 @@ -""" -Find Missing Number +"""Find Missing Number. Given a sequence of unique integers in the range [0..n] with one value missing, find and return that missing number. Two approaches are provided: @@ -32,6 +31,7 @@ def find_missing_number(nums: list[int]) -> int: 7 >>> find_missing_number([0]) 1 + """ missing = 0 for index, number in enumerate(nums): @@ -57,6 +57,7 @@ def find_missing_number2(nums: list[int]) -> int: 7 >>> find_missing_number2([0]) 1 + """ total = sum(nums) length = len(nums) diff --git a/algorithms/bit_manipulation/flip_bit_longest_sequence.py b/algorithms/bit_manipulation/flip_bit_longest_sequence.py index 53f542f2c..e46f392f8 100644 --- a/algorithms/bit_manipulation/flip_bit_longest_sequence.py +++ b/algorithms/bit_manipulation/flip_bit_longest_sequence.py @@ -1,5 +1,4 @@ -""" -Flip Bit Longest Sequence +"""Flip Bit Longest Sequence. Given an integer, find the length of the longest sequence of 1-bits you can create by flipping exactly one 0-bit to a 1-bit. @@ -32,6 +31,7 @@ def flip_bit_longest_seq(number: int) -> int: 8 >>> flip_bit_longest_seq(0) 1 + """ current_length = 0 previous_length = 0 diff --git a/algorithms/bit_manipulation/has_alternative_bit.py b/algorithms/bit_manipulation/has_alternative_bit.py index 484702732..f62416ece 100644 --- a/algorithms/bit_manipulation/has_alternative_bit.py +++ b/algorithms/bit_manipulation/has_alternative_bit.py @@ -1,5 +1,4 @@ -""" -Has Alternating Bits +"""Has Alternating Bits. Check whether a positive integer has alternating bits, meaning no two adjacent bits share the same value. @@ -28,6 +27,7 @@ def has_alternative_bit(number: int) -> bool: True >>> has_alternative_bit(7) False + """ first_bit = 0 second_bit = 0 @@ -57,6 +57,7 @@ def has_alternative_bit_fast(number: int) -> bool: True >>> has_alternative_bit_fast(7) False + """ mask_even_bits = int("aaaaaaaa", 16) # ...10101010 mask_odd_bits = int("55555555", 16) # ...01010101 diff --git a/algorithms/bit_manipulation/insert_bit.py b/algorithms/bit_manipulation/insert_bit.py index 0c4749ea3..5e71f2e4a 100644 --- a/algorithms/bit_manipulation/insert_bit.py +++ b/algorithms/bit_manipulation/insert_bit.py @@ -1,5 +1,4 @@ -""" -Insert Bit +"""Insert Bit. Insert one or more bits into an integer at a specific bit position. @@ -32,6 +31,7 @@ def insert_one_bit(number: int, bit: int, position: int) -> int: 45 >>> insert_one_bit(21, 0, 2) 41 + """ upper = number >> position upper = (upper << 1) | bit @@ -61,6 +61,7 @@ def insert_mult_bits(number: int, bits: int, length: int, position: int) -> int: 47 >>> insert_mult_bits(5, 7, 3, 3) 61 + """ upper = number >> position upper = (upper << length) | bits diff --git a/algorithms/bit_manipulation/power_of_two.py b/algorithms/bit_manipulation/power_of_two.py index 2a965ff56..f36bd1f95 100644 --- a/algorithms/bit_manipulation/power_of_two.py +++ b/algorithms/bit_manipulation/power_of_two.py @@ -1,5 +1,4 @@ -""" -Power of Two +"""Power of Two. Determine whether a given integer is a power of two using bit manipulation. A power of two has exactly one set bit, so ``n & (n - 1)`` clears that bit @@ -31,5 +30,6 @@ def is_power_of_two(number: int) -> bool: False >>> is_power_of_two(0) False + """ return number > 0 and not number & (number - 1) diff --git a/algorithms/bit_manipulation/remove_bit.py b/algorithms/bit_manipulation/remove_bit.py index d81b14113..03371ec2c 100644 --- a/algorithms/bit_manipulation/remove_bit.py +++ b/algorithms/bit_manipulation/remove_bit.py @@ -1,5 +1,4 @@ -""" -Remove Bit +"""Remove Bit. Remove a single bit at a specific position from an integer, shifting higher bits down to fill the gap. @@ -34,6 +33,7 @@ def remove_bit(number: int, position: int) -> int: 5 >>> remove_bit(21, 0) 10 + """ upper = number >> (position + 1) upper = upper << position diff --git a/algorithms/bit_manipulation/reverse_bits.py b/algorithms/bit_manipulation/reverse_bits.py index bac93e8d2..df068c7cd 100644 --- a/algorithms/bit_manipulation/reverse_bits.py +++ b/algorithms/bit_manipulation/reverse_bits.py @@ -1,5 +1,4 @@ -""" -Reverse Bits +"""Reverse Bits. Reverse the bits of a 32-bit unsigned integer. @@ -27,6 +26,7 @@ def reverse_bits(number: int) -> int: 964176192 >>> reverse_bits(0) 0 + """ result = 0 for _ in range(32): diff --git a/algorithms/bit_manipulation/single_number.py b/algorithms/bit_manipulation/single_number.py index 14552fd9e..94a618bb0 100644 --- a/algorithms/bit_manipulation/single_number.py +++ b/algorithms/bit_manipulation/single_number.py @@ -1,5 +1,4 @@ -""" -Single Number +"""Single Number. Given an array of integers where every element appears twice except for one, find the unique element using XOR. @@ -32,6 +31,7 @@ def single_number(nums: list[int]) -> int: 0 >>> single_number([101]) 101 + """ result = 0 for number in nums: diff --git a/algorithms/bit_manipulation/single_number2.py b/algorithms/bit_manipulation/single_number2.py index a6ca17d25..7e1fb362a 100644 --- a/algorithms/bit_manipulation/single_number2.py +++ b/algorithms/bit_manipulation/single_number2.py @@ -1,5 +1,4 @@ -""" -Single Number 2 +"""Single Number 2. Given an array of integers where every element appears three times except for one (which appears exactly once), find that unique element using @@ -32,6 +31,7 @@ def single_number2(nums: list[int]) -> int: Examples: >>> single_number2([4, 2, 3, 2, 1, 1, 4, 2, 4, 1]) 3 + """ ones, twos = 0, 0 for index in range(len(nums)): diff --git a/algorithms/bit_manipulation/single_number3.py b/algorithms/bit_manipulation/single_number3.py index 3a86b3186..8094b6047 100644 --- a/algorithms/bit_manipulation/single_number3.py +++ b/algorithms/bit_manipulation/single_number3.py @@ -1,5 +1,4 @@ -""" -Single Number 3 +"""Single Number 3. Given an array where exactly two elements appear once and all others appear exactly twice, find those two unique elements in O(n) time and @@ -31,6 +30,7 @@ def single_number3(nums: list[int]) -> list[int]: Examples: >>> sorted(single_number3([1, 2, 1, 3, 2, 5])) [3, 5] + """ xor_both = 0 for number in nums: diff --git a/algorithms/bit_manipulation/subsets.py b/algorithms/bit_manipulation/subsets.py index 75b6a81d5..aed83cffe 100644 --- a/algorithms/bit_manipulation/subsets.py +++ b/algorithms/bit_manipulation/subsets.py @@ -1,5 +1,4 @@ -""" -Subsets via Bit Manipulation +"""Subsets via Bit Manipulation. Generate all possible subsets of a set of distinct integers using bitmask enumeration. Each integer from 0 to 2^n - 1 represents a unique subset. @@ -29,6 +28,7 @@ def subsets(nums: list[int]) -> set[tuple[int, ...]]: Examples: >>> sorted(subsets([1, 2, 3])) [(), (1,), (1, 2), (1, 2, 3), (1, 3), (2,), (2, 3), (3,)] + """ length = len(nums) total = 1 << length diff --git a/algorithms/bit_manipulation/swap_pair.py b/algorithms/bit_manipulation/swap_pair.py index 4fb49edb9..ded32fa6b 100644 --- a/algorithms/bit_manipulation/swap_pair.py +++ b/algorithms/bit_manipulation/swap_pair.py @@ -1,5 +1,4 @@ -""" -Swap Pair +"""Swap Pair. Swap odd and even bits of an integer using bitmask operations. Bit 0 is swapped with bit 1, bit 2 with bit 3, and so on. @@ -32,6 +31,7 @@ def swap_pair(number: int) -> int: 41 >>> swap_pair(10) 5 + """ odd_bits = (number & int("AAAAAAAA", 16)) >> 1 even_bits = (number & int("55555555", 16)) << 1 diff --git a/algorithms/common/__init__.py b/algorithms/common/__init__.py index 3c4d2c95a..28fa9f64d 100644 --- a/algorithms/common/__init__.py +++ b/algorithms/common/__init__.py @@ -10,4 +10,4 @@ from algorithms.common.list_node import ListNode from algorithms.common.tree_node import TreeNode -__all__ = ["TreeNode", "ListNode", "Graph"] +__all__ = ["Graph", "ListNode", "TreeNode"] diff --git a/algorithms/common/graph.py b/algorithms/common/graph.py index 19bc721d9..ddee26a23 100644 --- a/algorithms/common/graph.py +++ b/algorithms/common/graph.py @@ -31,6 +31,7 @@ class Graph: >>> g = Graph.unweighted({"a": ["b", "c"], "b": ["c"]}) >>> g.adj["a"] {'b': 1, 'c': 1} + """ adj: dict[str, dict[str, float]] = field(default_factory=dict) @@ -51,9 +52,10 @@ def unweighted(cls, adj: dict[str, list[str]], directed: bool = True) -> Graph: >>> g = Graph.unweighted({"a": ["b", "c"], "b": ["c"]}) >>> g.adj["b"] {'c': 1} + """ weighted = { - node: {neighbor: 1 for neighbor in neighbors} + node: dict.fromkeys(neighbors, 1) for node, neighbors in adj.items() } return cls(adj=weighted, directed=directed) @@ -69,6 +71,7 @@ def nodes(self) -> set[str]: >>> g = Graph({"a": {"b": 1}}) >>> sorted(g.nodes()) ['a', 'b'] + """ all_nodes: set[str] = set(self.adj.keys()) for neighbors in self.adj.values(): @@ -90,6 +93,7 @@ def add_edge(self, source: str, target: str, weight: float = 1) -> None: >>> g.add_edge("a", "b", 5) >>> g.adj["a"]["b"] 5 + """ if source not in self.adj: self.adj[source] = {} diff --git a/algorithms/common/list_node.py b/algorithms/common/list_node.py index b33763cfb..1a3540045 100644 --- a/algorithms/common/list_node.py +++ b/algorithms/common/list_node.py @@ -25,6 +25,7 @@ class ListNode: 1 >>> head.next.val 2 + """ val: int = 0 diff --git a/algorithms/common/tree_node.py b/algorithms/common/tree_node.py index a9fc4cc78..3a7f85b2d 100644 --- a/algorithms/common/tree_node.py +++ b/algorithms/common/tree_node.py @@ -25,6 +25,7 @@ class TreeNode: 2 >>> root.right.val 3 + """ val: int = 0 diff --git a/algorithms/compression/elias.py b/algorithms/compression/elias.py index 695a43c1b..6c4c11c74 100644 --- a/algorithms/compression/elias.py +++ b/algorithms/compression/elias.py @@ -1,5 +1,4 @@ -""" -Elias Gamma and Delta Coding +"""Elias Gamma and Delta Coding. Universal codes for encoding positive integers. Elias gamma code uses a unary prefix followed by a binary suffix. Elias delta code nests gamma coding for @@ -14,11 +13,14 @@ from __future__ import annotations -from collections.abc import Callable -from math import log +from math import log2 +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable -def _log2(x: int | float) -> float: +def _log2(x: float) -> float: """Compute log base 2. Args: @@ -26,8 +28,9 @@ def _log2(x: int | float) -> float: Returns: The base-2 logarithm of x. + """ - return log(x, 2) + return log2(x) def _binary(x: int, length: int = 1) -> str: @@ -39,6 +42,7 @@ def _binary(x: int, length: int = 1) -> str: Returns: A binary string. + """ fmt = f"{{0:0{length}b}}" return fmt.format(x) @@ -52,6 +56,7 @@ def _unary(x: int) -> str: Returns: A unary-coded string (x-1 zeros followed by a one). + """ return (x - 1) * "0" + "1" @@ -68,9 +73,11 @@ def _elias_generic( Returns: The Elias-coded bit string. + """ if x <= 0: - raise ValueError("Elias coding is defined only for positive integers") + msg = "Elias coding is defined only for positive integers" + raise ValueError(msg) first_part = 1 + int(_log2(x)) remainder = x - 2 ** int(_log2(x)) @@ -94,6 +101,7 @@ def elias_gamma(x: int) -> str: '1' >>> elias_gamma(5) '00101' + """ return _elias_generic(_unary, x) @@ -112,5 +120,6 @@ def elias_delta(x: int) -> str: '1' >>> elias_delta(5) '01101' + """ return _elias_generic(elias_gamma, x) diff --git a/algorithms/compression/huffman_coding.py b/algorithms/compression/huffman_coding.py index 1b95e842b..55e87d2ad 100644 --- a/algorithms/compression/huffman_coding.py +++ b/algorithms/compression/huffman_coding.py @@ -1,5 +1,4 @@ -""" -Huffman Coding +"""Huffman Coding. An efficient method of lossless data compression. Symbols appearing more frequently are encoded with shorter bit strings while less frequent symbols @@ -64,6 +63,7 @@ def get_number_of_additional_bits_in_the_last_byte(self) -> int: Returns: The number of additional padding bits in the last byte. + """ bin_num = self.get_bit() + self.get_bit() + self.get_bit() return int(bin_num, 2) @@ -73,6 +73,7 @@ def load_tree(self) -> Node: Returns: The root node of the reconstructed tree. + """ node_stack: deque[Node] = deque() queue_leaves: deque[Node] = deque() @@ -102,6 +103,7 @@ def _fill_tree(self, leaves_queue: deque[Node]) -> None: Args: leaves_queue: Queue of leaf nodes to populate. + """ leaves_queue.reverse() while leaves_queue: @@ -117,6 +119,7 @@ def _load_byte(self, buff_limit: int = 8) -> bool: Returns: True if enough bits are available for reading. + """ if len(self.buffer) <= buff_limit: byte = self.file.read(1) @@ -134,6 +137,7 @@ def get_bit(self, buff_limit: int = 8) -> str | int: Returns: A '0' or '1' character, or -1 if at end of file. + """ if self._load_byte(buff_limit): return self.buffer.pop(0) @@ -144,6 +148,7 @@ def get_byte(self) -> str | int: Returns: An 8-character binary string, or -1 if at end of file. + """ if self._load_byte(): byte_list = self.buffer[:8] @@ -165,6 +170,7 @@ def write_char(self, char: str) -> None: Args: char: A single character to write. + """ self.write_int(ord(char)) @@ -173,6 +179,7 @@ def write_int(self, num: int) -> None: Args: num: An integer (0-255) to write. + """ bin_int = f"{num:08b}" self.write_bits(bin_int) @@ -182,6 +189,7 @@ def write_bits(self, bits: str) -> None: Args: bits: A string of '0' and '1' characters. + """ self.saved_bits += len(bits) self.buffer += bits @@ -195,6 +203,7 @@ def save_tree(self, tree: Node) -> None: Args: tree: The root node of the Huffman tree. + """ signs: list[int] = [] tree_code = "" @@ -220,6 +229,7 @@ def _save_information_about_additional_bits(self, additional_bits: int) -> None: Args: additional_bits: The number of padding bits appended. + """ self.file.seek(0) first_byte_raw = self.file.read(1) @@ -252,6 +262,7 @@ def find(self, bit: str) -> bool: Returns: True if a symbol was found at the current node. + """ if bit == "0": self.current_node = self.current_node.left @@ -271,6 +282,7 @@ def _reset(self, found: int | str = "") -> None: Args: found: The decoded symbol value. + """ self.found = found self.current_node = self.root @@ -289,13 +301,14 @@ def decode_file(file_in_name: str, file_out_name: str) -> None: Args: file_in_name: Path to the encoded input file. file_out_name: Path to the decoded output file. + """ with open(file_in_name, "rb") as file_in, open(file_out_name, "wb") as file_out: reader = HuffmanReader(file_in) additional_bits = reader.get_number_of_additional_bits_in_the_last_byte() tree = reader.load_tree() HuffmanCoding._decode_and_write_signs_to_file( - file_out, reader, tree, additional_bits + file_out, reader, tree, additional_bits, ) @staticmethod @@ -312,6 +325,7 @@ def _decode_and_write_signs_to_file( reader: The HuffmanReader providing encoded bits. tree: The root of the Huffman tree. additional_bits: Number of padding bits in the last byte. + """ tree_finder = TreeFinder(tree) is_end_of_file = False @@ -337,6 +351,7 @@ def encode_file(file_in_name: str, file_out_name: str) -> None: Args: file_in_name: Path to the raw input file. file_out_name: Path to the encoded output file. + """ with ( open(file_in_name, "rb") as file_in, @@ -355,7 +370,7 @@ def encode_file(file_in_name: str, file_out_name: str) -> None: @staticmethod def _encode_and_write_signs_to_file( - file: object, writer: HuffmanWriter, codes: dict[int, str] + file: object, writer: HuffmanWriter, codes: dict[int, str], ) -> None: """Read bytes from file and write their Huffman codes. @@ -363,6 +378,7 @@ def _encode_and_write_signs_to_file( file: The input file object. writer: The HuffmanWriter for output. codes: Mapping of byte values to Huffman code strings. + """ sign = file.read(1) while sign: @@ -379,6 +395,7 @@ def _get_char_frequency(file: object) -> dict[int, int]: Returns: A dict mapping byte values to their frequencies. + """ is_end_of_file = False signs_frequency: dict[int, int] = defaultdict(lambda: 0) @@ -401,6 +418,7 @@ def _generate_codes(tree: Node) -> dict[int, str]: Returns: A dict mapping byte values to their binary code strings. + """ codes: dict[int, str] = {} HuffmanCoding._go_through_tree_and_create_codes(tree, "", codes) @@ -415,6 +433,7 @@ def _create_tree(signs_frequency: dict[int, int]) -> Node: Returns: The root node of the constructed Huffman tree. + """ nodes = [ Node(frequency=frequency, sign=char_int) @@ -436,7 +455,7 @@ def _create_tree(signs_frequency: dict[int, int]) -> Node: @staticmethod def _go_through_tree_and_create_codes( - tree: Node, code: str, dict_codes: dict[int, str] + tree: Node, code: str, dict_codes: dict[int, str], ) -> None: """Recursively traverse the tree to build code mappings. @@ -444,16 +463,17 @@ def _go_through_tree_and_create_codes( tree: The current node being visited. code: The accumulated bit string for this path. dict_codes: The output dict to populate. + """ if tree.sign is not None: dict_codes[tree.sign] = code if tree.left: HuffmanCoding._go_through_tree_and_create_codes( - tree.left, code + "0", dict_codes + tree.left, code + "0", dict_codes, ) if tree.right: HuffmanCoding._go_through_tree_and_create_codes( - tree.right, code + "1", dict_codes + tree.right, code + "1", dict_codes, ) diff --git a/algorithms/compression/lzw_compression.py b/algorithms/compression/lzw_compression.py index 01d7b38c6..42985a31b 100644 --- a/algorithms/compression/lzw_compression.py +++ b/algorithms/compression/lzw_compression.py @@ -1,5 +1,4 @@ -""" -Lempel-Ziv-Welch (LZW) Compression +"""Lempel-Ziv-Welch (LZW) Compression. A dictionary-based lossless compression algorithm. It builds a dictionary of substrings during encoding and replaces repeated substrings with dictionary @@ -24,6 +23,7 @@ def _build_initial_dictionary(data: str) -> dict[str, int]: Returns: A dictionary mapping characters to integer codes. + """ return {char: index for index, char in enumerate(sorted(set(data)))} @@ -46,6 +46,7 @@ def lzw_encode(data: str) -> tuple[list[int], dict[int, str]]: {0: 'A', 1: 'B'} >>> lzw_encode("") ([], {}) + """ if not data: return [], {} @@ -92,6 +93,7 @@ def lzw_decode(encoded: list[int], initial_dictionary: dict[int, str]) -> str: 'ABABABA' >>> lzw_decode([], {}) '' + """ if not encoded: return "" @@ -107,7 +109,8 @@ def lzw_decode(encoded: list[int], initial_dictionary: dict[int, str]) -> str: elif code == next_code and previous: current = previous + previous[0] else: - raise ValueError(f"Invalid LZW code: {code}") + msg = f"Invalid LZW code: {code}" + raise ValueError(msg) decoded += current diff --git a/algorithms/compression/rle_compression.py b/algorithms/compression/rle_compression.py index ee9a2421a..5f9d75a0c 100644 --- a/algorithms/compression/rle_compression.py +++ b/algorithms/compression/rle_compression.py @@ -1,5 +1,4 @@ -""" -Run-Length Encoding (RLE) +"""Run-Length Encoding (RLE). A simple lossless compression algorithm that encodes consecutive repeated characters as a count followed by the character. Decompression fully recovers @@ -29,6 +28,7 @@ def encode_rle(data: str) -> str: '3a2b1c' >>> encode_rle("") '' + """ if not data: return "" @@ -63,6 +63,7 @@ def decode_rle(data: str) -> str: 'aaabbc' >>> decode_rle("") '' + """ decoded: str = "" count: str = "" diff --git a/algorithms/data_structures/__init__.py b/algorithms/data_structures/__init__.py index 1e8d826ba..d0bd43b80 100644 --- a/algorithms/data_structures/__init__.py +++ b/algorithms/data_structures/__init__.py @@ -43,36 +43,36 @@ from algorithms.data_structures.union_find import Union __all__ = [ + "BST", "AbstractHeap", "AbstractQueue", "AbstractStack", "ArrayQueue", "ArrayStack", + # Tree data structures + "AvlTree", + "BTree", "BinaryHeap", "DirectedEdge", "DirectedGraph", "DoublyLinkedListNode", + "Fenwick_Tree", "HashTable", + "KDTree", "LinkedListQueue", "LinkedListStack", "Node", "PriorityQueue", "PriorityQueueNode", "QueueNode", + "RBTree", "ResizableHashTable", + "SegmentTree", + "SegmentTreeRecursive", "SeparateChainingHashTable", "SinglyLinkedListNode", + "SqrtDecomposition", "StackNode", - "Union", - # Tree data structures - "AvlTree", - "BTree", - "BST", - "Fenwick_Tree", - "RBTree", - "SegmentTree", - "SegmentTreeRecursive", "Trie", - "KDTree", - "SqrtDecomposition", + "Union", ] diff --git a/algorithms/data_structures/avl_tree.py b/algorithms/data_structures/avl_tree.py index 7be8cbe0e..9d6518113 100644 --- a/algorithms/data_structures/avl_tree.py +++ b/algorithms/data_structures/avl_tree.py @@ -1,23 +1,19 @@ -"""Imports TreeNodes""" +"""Imports TreeNodes.""" from algorithms.common.tree_node import TreeNode class AvlTree: - """ - An avl tree. - """ + """An avl tree.""" - def __init__(self): + def __init__(self) -> None: # Root node of the tree. self.node = None self.height = -1 self.balance = 0 - def insert(self, key): - """ - Insert new key into node - """ + def insert(self, key) -> None: + """Insert new key into node.""" # Create new node node = TreeNode(key) if not self.node: @@ -30,10 +26,8 @@ def insert(self, key): self.node.right.insert(key) self.re_balance() - def re_balance(self): - """ - Re balance tree. After inserting or deleting a node, - """ + def re_balance(self) -> None: + """Re balance tree. After inserting or deleting a node.""" self.update_heights(recursive=False) self.update_balances(False) @@ -56,10 +50,8 @@ def re_balance(self): self.update_heights() self.update_balances() - def update_heights(self, recursive=True): - """ - Update tree height - """ + def update_heights(self, recursive=True) -> None: + """Update tree height.""" if self.node: if recursive: if self.node.left: @@ -71,11 +63,8 @@ def update_heights(self, recursive=True): else: self.height = -1 - def update_balances(self, recursive=True): - """ - Calculate tree balance factor - - """ + def update_balances(self, recursive=True) -> None: + """Calculate tree balance factor.""" if self.node: if recursive: if self.node.left: @@ -87,10 +76,8 @@ def update_balances(self, recursive=True): else: self.balance = 0 - def rotate_right(self): - """ - Right rotation - """ + def rotate_right(self) -> None: + """Right rotation.""" new_root = self.node.left.node new_left_sub = new_root.right.node old_root = self.node @@ -99,10 +86,8 @@ def rotate_right(self): old_root.left.node = new_left_sub new_root.right.node = old_root - def rotate_left(self): - """ - Left rotation - """ + def rotate_left(self) -> None: + """Left rotation.""" new_root = self.node.right.node new_left_sub = new_root.left.node old_root = self.node @@ -112,9 +97,7 @@ def rotate_left(self): new_root.left.node = old_root def in_order_traverse(self): - """ - In-order traversal of the tree - """ + """In-order traversal of the tree.""" result = [] if not self.node: diff --git a/algorithms/data_structures/b_tree.py b/algorithms/data_structures/b_tree.py index 369e4cb36..baad606dc 100644 --- a/algorithms/data_structures/b_tree.py +++ b/algorithms/data_structures/b_tree.py @@ -1,5 +1,4 @@ -""" -B-Tree +"""B-Tree. A self-balancing tree data structure optimized for disk operations. Each node (except root) contains at least t-1 keys and at most 2t-1 keys, where t is the @@ -22,6 +21,7 @@ class Node: >>> node = Node() >>> node.keys [] + """ def __init__(self) -> None: @@ -33,6 +33,7 @@ def __repr__(self) -> str: Returns: A string showing the node's keys. + """ return f"" @@ -42,6 +43,7 @@ def is_leaf(self) -> bool: Returns: True if the node has no children, False otherwise. + """ return len(self.children) == 0 @@ -57,6 +59,7 @@ class BTree: >>> bt.insert_key(10) >>> bt.find(10) True + """ def __init__(self, t_val: int = 2) -> None: @@ -70,6 +73,7 @@ def _split_child(self, parent: Node, child_index: int) -> None: Args: parent: The parent node whose child is being split. child_index: The index of the child to split. + """ new_right_child = Node() half_max = self.max_number_of_keys // 2 @@ -90,6 +94,7 @@ def insert_key(self, key: int) -> None: Args: key: The key to insert. + """ if len(self.root.keys) >= self.max_number_of_keys: new_root = Node() @@ -106,6 +111,7 @@ def _insert_to_nonfull_node(self, node: Node, key: int) -> None: Args: node: The non-full node to insert into. key: The key to insert. + """ i = len(node.keys) - 1 while i >= 0 and node.keys[i] >= key: @@ -136,6 +142,7 @@ def find(self, key: int) -> bool: True >>> bt.find(3) False + """ current_node = self.root while True: @@ -153,6 +160,7 @@ def remove_key(self, key: int) -> None: Args: key: The key to remove. + """ self._remove_key(self.root, key) @@ -165,6 +173,7 @@ def _remove_key(self, node: Node, key: int) -> bool: Returns: True if the key was found and removed, False otherwise. + """ try: key_index = node.keys.index(key) @@ -177,17 +186,15 @@ def _remove_key(self, node: Node, key: int) -> bool: except ValueError: if node.is_leaf: return False - else: - i = 0 - number_of_keys = len(node.keys) - while i < number_of_keys and key > node.keys[i]: - i += 1 + i = 0 + number_of_keys = len(node.keys) + while i < number_of_keys and key > node.keys[i]: + i += 1 - action_performed = self._repair_tree(node, i) - if action_performed: - return self._remove_key(node, key) - else: - return self._remove_key(node.children[i], key) + action_performed = self._repair_tree(node, i) + if action_performed: + return self._remove_key(node, key) + return self._remove_key(node.children[i], key) def _repair_tree(self, node: Node, child_index: int) -> bool: """Repair the tree after a deletion to maintain B-tree properties. @@ -198,6 +205,7 @@ def _repair_tree(self, node: Node, child_index: int) -> bool: Returns: True if a structural repair was performed, False otherwise. + """ child = node.children[child_index] if self.min_numbers_of_keys < len(child.keys) <= self.max_number_of_keys: @@ -230,6 +238,7 @@ def _rotate_left(self, parent_node: Node, child_index: int) -> None: Args: parent_node: The parent node. child_index: The index of the child receiving the key. + """ new_child_key = parent_node.keys[child_index] new_parent_key = parent_node.children[child_index + 1].keys.pop(0) @@ -246,6 +255,7 @@ def _rotate_right(self, parent_node: Node, child_index: int) -> None: Args: parent_node: The parent node. child_index: The index of the child receiving the key. + """ parent_key = parent_node.keys[child_index - 1] new_parent_key = parent_node.children[child_index - 1].keys.pop() @@ -257,7 +267,7 @@ def _rotate_right(self, parent_node: Node, child_index: int) -> None: parent_node.children[child_index].children.insert(0, ownerless_child) def _merge( - self, parent_node: Node, to_merge_index: int, transferred_child_index: int + self, parent_node: Node, to_merge_index: int, transferred_child_index: int, ) -> None: """Merge two child nodes and a parent key into a single node. @@ -265,6 +275,7 @@ def _merge( parent_node: The parent node. to_merge_index: Index of the child that receives the merged data. transferred_child_index: Index of the child being merged in. + """ from_merge_node = parent_node.children.pop(transferred_child_index) parent_key_to_merge = parent_node.keys.pop(to_merge_index) @@ -279,13 +290,14 @@ def _merge( self.root = to_merge_node def _remove_from_nonleaf_node( - self, node: Node, key_index: int + self, node: Node, key_index: int, ) -> None: """Remove a key from a non-leaf node by replacing with predecessor/successor. Args: node: The non-leaf node containing the key. key_index: The index of the key to remove. + """ key = node.keys[key_index] left_subtree = node.children[key_index] @@ -293,13 +305,14 @@ def _remove_from_nonleaf_node( largest_key = self._find_largest_and_delete_in_left_subtree(left_subtree) elif len(node.children[key_index + 1].keys) > self.min_numbers_of_keys: largest_key = self._find_largest_and_delete_in_right_subtree( - node.children[key_index + 1] + node.children[key_index + 1], ) else: self._merge(node, key_index, key_index + 1) return self._remove_key(node, key) node.keys[key_index] = largest_key + return None def _find_largest_and_delete_in_left_subtree(self, node: Node) -> int: """Find and remove the largest key in the left subtree. @@ -309,16 +322,15 @@ def _find_largest_and_delete_in_left_subtree(self, node: Node) -> int: Returns: The largest key that was removed. + """ if node.is_leaf: return node.keys.pop() - else: - ch_index = len(node.children) - 1 - self._repair_tree(node, ch_index) - largest_key_in_subtree = self._find_largest_and_delete_in_left_subtree( - node.children[len(node.children) - 1] - ) - return largest_key_in_subtree + ch_index = len(node.children) - 1 + self._repair_tree(node, ch_index) + return self._find_largest_and_delete_in_left_subtree( + node.children[len(node.children) - 1], + ) def _find_largest_and_delete_in_right_subtree(self, node: Node) -> int: """Find and remove the smallest key in the right subtree. @@ -328,16 +340,15 @@ def _find_largest_and_delete_in_right_subtree(self, node: Node) -> int: Returns: The smallest key that was removed. + """ if node.is_leaf: return node.keys.pop(0) - else: - ch_index = 0 - self._repair_tree(node, ch_index) - largest_key_in_subtree = self._find_largest_and_delete_in_right_subtree( - node.children[0] - ) - return largest_key_in_subtree + ch_index = 0 + self._repair_tree(node, ch_index) + return self._find_largest_and_delete_in_right_subtree( + node.children[0], + ) def traverse_tree(self) -> list: """Traverse the B-tree in order and return all keys. @@ -350,6 +361,7 @@ def traverse_tree(self) -> list: >>> for k in [3, 1, 2]: bt.insert_key(k) >>> bt.traverse_tree() [1, 2, 3] + """ result: list = [] self._traverse_tree(self.root, result) @@ -361,6 +373,7 @@ def _traverse_tree(self, node: Node, result: list) -> None: Args: node: The root of the subtree to traverse. result: The list to append keys to. + """ if node.is_leaf: result.extend(node.keys) diff --git a/algorithms/data_structures/bst.py b/algorithms/data_structures/bst.py index df7cccbd6..cd8a0c57c 100644 --- a/algorithms/data_structures/bst.py +++ b/algorithms/data_structures/bst.py @@ -47,10 +47,9 @@ def _recur_search(self, root: Node | None, data: int) -> bool: return False if root.data == data: return True - elif data > root.data: + if data > root.data: return self._recur_search(root.right, data) - else: - return self._recur_search(root.left, data) + return self._recur_search(root.left, data) def insert(self, data: int) -> bool: """Insert data into the tree. @@ -60,25 +59,21 @@ def insert(self, data: int) -> bool: """ if self.root: return self._recur_insert(self.root, data) - else: - self.root = Node(data) - return True + self.root = Node(data) + return True def _recur_insert(self, root: Node, data: int) -> bool: if root.data == data: return False - elif data < root.data: + if data < root.data: if root.left: return self._recur_insert(root.left, data) - else: - root.left = Node(data) - return True - else: - if root.right: - return self._recur_insert(root.right, data) - else: - root.right = Node(data) - return True + root.left = Node(data) + return True + if root.right: + return self._recur_insert(root.right, data) + root.right = Node(data) + return True def preorder(self, root: Node | None) -> list[int]: """Return list of node values in preorder (root, left, right).""" diff --git a/algorithms/data_structures/fenwick_tree.py b/algorithms/data_structures/fenwick_tree.py index f80e5a614..d05f5edce 100644 --- a/algorithms/data_structures/fenwick_tree.py +++ b/algorithms/data_structures/fenwick_tree.py @@ -1,5 +1,4 @@ -""" -Fenwick Tree / Binary Indexed Tree +"""Fenwick Tree / Binary Indexed Tree. Consider we have an array arr[0 . . . n-1]. We would like to 1. Compute the sum of the first i elements. @@ -28,17 +27,15 @@ class Fenwick_Tree: # noqa: N801 - def __init__(self, freq): + def __init__(self, freq) -> None: self.arr = freq self.n = len(freq) def get_sum(self, bit_tree, i): - """ - Returns sum of arr[0..index]. This function assumes + """Returns sum of arr[0..index]. This function assumes that the array is preprocessed and partial sums of array elements are stored in bit_tree[]. """ - s = 0 # index in bit_tree[] is 1 more than the index in arr[] @@ -53,13 +50,11 @@ def get_sum(self, bit_tree, i): i -= i & (-i) return s - def update_bit(self, bit_tree, i, v): - """ - Updates a node in Binary Index Tree (bit_tree) at + def update_bit(self, bit_tree, i, v) -> None: + """Updates a node in Binary Index Tree (bit_tree) at given index in bit_tree. The given value 'val' is added to bit_tree[i] and all of its ancestors in tree. """ - # index in bit_ree[] is 1 more than the index in arr[] i += 1 @@ -72,11 +67,9 @@ def update_bit(self, bit_tree, i, v): i += i & (-i) def construct(self): - """ - Constructs and returns a Binary Indexed Tree for given + """Constructs and returns a Binary Indexed Tree for given array of size n. """ - # Create and initialize bit_ree[] as 0 bit_tree = [0] * (self.n + 1) diff --git a/algorithms/data_structures/graph.py b/algorithms/data_structures/graph.py index 48b2332ba..f9e6de722 100644 --- a/algorithms/data_structures/graph.py +++ b/algorithms/data_structures/graph.py @@ -1,5 +1,4 @@ -""" -Graph Data Structures +"""Graph Data Structures. Reusable classes for representing nodes, directed edges and directed graphs. These can be shared across graph algorithms. @@ -23,6 +22,7 @@ def get_name(obj: object) -> str: Returns: The string name. + """ if isinstance(obj, Node): return obj.name @@ -82,6 +82,7 @@ def __init__(self, load_dict: dict[str, list[str]] | None = None) -> None: Args: load_dict: Optional adjacency dict ``{vertex: [neighbours]}``. + """ if load_dict is None: load_dict = {} @@ -106,6 +107,7 @@ def add_node(self, node_name: str) -> Node: Returns: The Node instance. + """ try: return self.nodes[self.nodes.index(node_name)] @@ -120,6 +122,7 @@ def add_edge(self, node_name_from: str, node_name_to: str) -> None: Args: node_name_from: Source node name. node_name_to: Target node name. + """ try: node_from = self.nodes[self.nodes.index(node_name_from)] diff --git a/algorithms/data_structures/hash_table.py b/algorithms/data_structures/hash_table.py index fc866a271..530520390 100644 --- a/algorithms/data_structures/hash_table.py +++ b/algorithms/data_structures/hash_table.py @@ -1,5 +1,4 @@ -""" -Hash Table (Open Addressing) +"""Hash Table (Open Addressing). Hash map implementation using open addressing with linear probing for collision resolution. Includes a resizable variant that doubles @@ -23,6 +22,7 @@ class HashTable: >>> ht.put(1, 'one') >>> ht.get(1) 'one' + """ _empty = object() @@ -33,6 +33,7 @@ def __init__(self, size: int = 11) -> None: Args: size: Number of slots in the underlying array. + """ self.size = size self._len = 0 @@ -48,6 +49,7 @@ def put(self, key: int, value: object) -> None: Raises: ValueError: If the table is full. + """ initial_hash = hash_ = self.hash(key) @@ -57,7 +59,7 @@ def put(self, key: int, value: object) -> None: self._values[hash_] = value self._len += 1 return - elif self._keys[hash_] == key: + if self._keys[hash_] == key: self._keys[hash_] = key self._values[hash_] = value return @@ -65,7 +67,8 @@ def put(self, key: int, value: object) -> None: hash_ = self._rehash(hash_) if initial_hash == hash_: - raise ValueError("Table is full") + msg = "Table is full" + raise ValueError(msg) def get(self, key: int) -> object | None: """Retrieve the value for a given key. @@ -75,12 +78,13 @@ def get(self, key: int) -> object | None: Returns: The value associated with the key, or None if not found. + """ initial_hash = hash_ = self.hash(key) while True: if self._keys[hash_] is self._empty: return None - elif self._keys[hash_] == key: + if self._keys[hash_] == key: return self._values[hash_] hash_ = self._rehash(hash_) @@ -92,12 +96,13 @@ def del_(self, key: int) -> None: Args: key: The key to delete. + """ initial_hash = hash_ = self.hash(key) while True: if self._keys[hash_] is self._empty: - return None - elif self._keys[hash_] == key: + return + if self._keys[hash_] == key: self._keys[hash_] = self._deleted self._values[hash_] = self._deleted self._len -= 1 @@ -105,7 +110,7 @@ def del_(self, key: int) -> None: hash_ = self._rehash(hash_) if initial_hash == hash_: - return None + return def hash(self, key: int) -> int: """Compute the hash index for a key. @@ -115,6 +120,7 @@ def hash(self, key: int) -> int: Returns: Index into the internal array. + """ return key % self.size @@ -126,6 +132,7 @@ def _rehash(self, old_hash: int) -> int: Returns: Next index to probe. + """ return (old_hash + 1) % self.size @@ -150,6 +157,7 @@ class ResizableHashTable(HashTable): >>> rht.put(1, 'a') >>> rht.get(1) 'a' + """ MIN_SIZE = 8 @@ -163,6 +171,7 @@ def put(self, key: int, value: object) -> None: Args: key: The key to insert. value: The value associated with the key. + """ super().put(key, value) if len(self) >= (self.size * 2) / 3: diff --git a/algorithms/data_structures/heap.py b/algorithms/data_structures/heap.py index 92368826d..e4de67f2c 100644 --- a/algorithms/data_structures/heap.py +++ b/algorithms/data_structures/heap.py @@ -1,5 +1,4 @@ -r""" -Binary Heap +r"""Binary Heap. A min heap is a complete binary tree where each node is smaller than its children. The root is the minimum element. Uses an array @@ -29,6 +28,7 @@ def perc_up(self, index: int) -> None: Args: index: Index of the element to percolate up. + """ @abstractmethod @@ -37,6 +37,7 @@ def insert(self, val: int) -> None: Args: val: The value to insert. + """ @abstractmethod @@ -45,6 +46,7 @@ def perc_down(self, index: int) -> None: Args: index: Index of the element to percolate down. + """ @abstractmethod @@ -56,6 +58,7 @@ def min_child(self, index: int) -> int: Returns: Index of the smaller child. + """ @abstractmethod @@ -64,6 +67,7 @@ def remove_min(self) -> int: Returns: The minimum value in the heap. + """ @@ -76,6 +80,7 @@ class BinaryHeap(AbstractHeap): >>> heap.insert(3) >>> heap.remove_min() 3 + """ def __init__(self) -> None: @@ -88,6 +93,7 @@ def perc_up(self, index: int) -> None: Args: index: Index of the element to percolate up. + """ while index // 2 > 0: if self.heap[index] < self.heap[index // 2]: @@ -104,6 +110,7 @@ def insert(self, val: int) -> None: Args: val: The value to insert. + """ self.heap.append(val) self.current_size = self.current_size + 1 @@ -117,6 +124,7 @@ def min_child(self, index: int) -> int: Returns: Index of the smaller child. + """ if 2 * index + 1 > self.current_size: return 2 * index @@ -129,6 +137,7 @@ def perc_down(self, index: int) -> None: Args: index: Index of the element to percolate down. + """ while 2 * index <= self.current_size: smaller_child = self.min_child(index) @@ -144,6 +153,7 @@ def remove_min(self) -> int: Returns: The minimum value. + """ ret = self.heap[1] self.heap[1] = self.heap[self.current_size] diff --git a/algorithms/data_structures/iterative_segment_tree.py b/algorithms/data_structures/iterative_segment_tree.py index 14498c0a8..813157b3a 100644 --- a/algorithms/data_structures/iterative_segment_tree.py +++ b/algorithms/data_structures/iterative_segment_tree.py @@ -1,9 +1,8 @@ -""" -SegmentTree creates a segment tree with a given array and a "commutative" function, +"""SegmentTree creates a segment tree with a given array and a "commutative" function, this non-recursive version uses less memory than the recursive version and include: 1. range queries in log(N) time 2. update an element in log(N) time -the function should be commutative and takes 2 values and returns the same type value +the function should be commutative and takes 2 values and returns the same type value. Examples - mytree = SegmentTree([2, 4, 5, 3, 4],max) @@ -24,17 +23,17 @@ class SegmentTree: - def __init__(self, arr, function): + def __init__(self, arr, function) -> None: self.tree = [None for _ in range(len(arr))] + arr self.size = len(arr) self.fn = function self.build_tree() - def build_tree(self): + def build_tree(self) -> None: for i in range(self.size - 1, 0, -1): self.tree[i] = self.fn(self.tree[i * 2], self.tree[i * 2 + 1]) - def update(self, p, v): + def update(self, p, v) -> None: p += self.size self.tree[p] = v while p > 1: diff --git a/algorithms/data_structures/kd_tree.py b/algorithms/data_structures/kd_tree.py index ed32e404a..d9256d7cb 100644 --- a/algorithms/data_structures/kd_tree.py +++ b/algorithms/data_structures/kd_tree.py @@ -14,7 +14,7 @@ class KDNode: """A single node in a KD-tree.""" - __slots__ = ("point", "left", "right", "axis") + __slots__ = ("axis", "left", "point", "right") def __init__( self, diff --git a/algorithms/data_structures/linked_list.py b/algorithms/data_structures/linked_list.py index 992173ec5..cc07ab8ac 100644 --- a/algorithms/data_structures/linked_list.py +++ b/algorithms/data_structures/linked_list.py @@ -1,5 +1,4 @@ -""" -Linked List Node Definitions +"""Linked List Node Definitions. Basic node classes for singly and doubly linked lists, serving as foundational building blocks for linked list algorithms. @@ -20,6 +19,7 @@ class SinglyLinkedListNode: Attributes: value: The value stored in the node. next: Reference to the next node, or None. + """ def __init__(self, value: object) -> None: @@ -34,6 +34,7 @@ class DoublyLinkedListNode: value: The value stored in the node. next: Reference to the next node, or None. prev: Reference to the previous node, or None. + """ def __init__(self, value: object) -> None: diff --git a/algorithms/data_structures/priority_queue.py b/algorithms/data_structures/priority_queue.py index b10dcb2ca..77c537a24 100644 --- a/algorithms/data_structures/priority_queue.py +++ b/algorithms/data_structures/priority_queue.py @@ -1,5 +1,4 @@ -""" -Priority Queue (Linear Array) +"""Priority Queue (Linear Array). A priority queue implementation using a sorted linear array. Elements are inserted in order so that extraction of the minimum is O(1). @@ -14,8 +13,10 @@ from __future__ import annotations import itertools -from collections.abc import Iterable -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Iterable class PriorityQueueNode: @@ -24,6 +25,7 @@ class PriorityQueueNode: Args: data: The stored value. priority: The priority of this node. + """ def __init__(self, data: Any, priority: Any) -> None: @@ -35,6 +37,7 @@ def __repr__(self) -> str: Returns: Formatted string with data and priority. + """ return f"{self.data}: {self.priority}" @@ -48,6 +51,7 @@ class PriorityQueue: 1 >>> pq.size() 2 + """ def __init__( @@ -60,6 +64,7 @@ def __init__( Args: items: Initial items to insert. priorities: Corresponding priorities; defaults to item values. + """ self.priority_queue_list: list[PriorityQueueNode] = [] if items is None: @@ -74,6 +79,7 @@ def __repr__(self) -> str: Returns: Formatted string. + """ return f"PriorityQueue({self.priority_queue_list!r})" @@ -82,6 +88,7 @@ def size(self) -> int: Returns: The queue size. + """ return len(self.priority_queue_list) @@ -91,6 +98,7 @@ def push(self, item: Any, priority: Any = None) -> None: Args: item: The value to insert. priority: Priority value; defaults to the item itself. + """ priority = item if priority is None else priority node = PriorityQueueNode(item, priority) @@ -105,5 +113,6 @@ def pop(self) -> Any: Returns: The data of the lowest-priority node. + """ return self.priority_queue_list.pop().data diff --git a/algorithms/data_structures/queue.py b/algorithms/data_structures/queue.py index 3633e067f..9d5df5583 100644 --- a/algorithms/data_structures/queue.py +++ b/algorithms/data_structures/queue.py @@ -1,5 +1,4 @@ -""" -Queue Abstract Data Type +"""Queue Abstract Data Type. Implementations of the queue ADT using both a fixed-size array and a linked list. Both support enqueue, dequeue, peek, is_empty, len, and iter. @@ -14,7 +13,10 @@ from __future__ import annotations from abc import ABCMeta, abstractmethod -from collections.abc import Iterator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator class AbstractQueue(metaclass=ABCMeta): @@ -31,6 +33,7 @@ def is_empty(self) -> bool: Returns: True if the queue has no elements. + """ return self._size == 0 @@ -59,6 +62,7 @@ class ArrayQueue(AbstractQueue): >>> q.enqueue(1) >>> q.dequeue() 1 + """ def __init__(self, capacity: int = 10) -> None: @@ -66,6 +70,7 @@ def __init__(self, capacity: int = 10) -> None: Args: capacity: Initial capacity of the underlying array. + """ super().__init__() self._array: list[object | None] = [None] * capacity @@ -85,6 +90,7 @@ def enqueue(self, value: object) -> None: Args: value: The value to enqueue. + """ if self._rear == len(self._array): self._expand() @@ -100,9 +106,11 @@ def dequeue(self) -> object: Raises: IndexError: If the queue is empty. + """ if self.is_empty(): - raise IndexError("Queue is empty") + msg = "Queue is empty" + raise IndexError(msg) value = self._array[self._front] self._array[self._front] = None self._front += 1 @@ -117,9 +125,11 @@ def peek(self) -> object: Raises: IndexError: If the queue is empty. + """ if self.is_empty(): - raise IndexError("Queue is empty") + msg = "Queue is empty" + raise IndexError(msg) return self._array[self._front] def _expand(self) -> None: @@ -143,6 +153,7 @@ class LinkedListQueue(AbstractQueue): >>> q.enqueue(1) >>> q.dequeue() 1 + """ def __init__(self) -> None: @@ -163,6 +174,7 @@ def enqueue(self, value: object) -> None: Args: value: The value to enqueue. + """ node = QueueNode(value) if self._front is None: @@ -181,9 +193,11 @@ def dequeue(self) -> object: Raises: IndexError: If the queue is empty. + """ if self.is_empty(): - raise IndexError("Queue is empty") + msg = "Queue is empty" + raise IndexError(msg) value = self._front.value if self._front is self._rear: self._front = None @@ -201,7 +215,9 @@ def peek(self) -> object: Raises: IndexError: If the queue is empty. + """ if self.is_empty(): - raise IndexError("Queue is empty") + msg = "Queue is empty" + raise IndexError(msg) return self._front.value diff --git a/algorithms/data_structures/red_black_tree.py b/algorithms/data_structures/red_black_tree.py index 270a2c0de..09cd98b4a 100644 --- a/algorithms/data_structures/red_black_tree.py +++ b/algorithms/data_structures/red_black_tree.py @@ -1,10 +1,8 @@ -""" -Implementation of Red-Black tree. -""" +"""Implementation of Red-Black tree.""" class RBNode: - def __init__(self, val, is_red, parent=None, left=None, right=None): + def __init__(self, val, is_red, parent=None, left=None, right=None) -> None: self.val = val self.parent = parent self.left = left @@ -13,52 +11,50 @@ def __init__(self, val, is_red, parent=None, left=None, right=None): class RBTree: - def __init__(self): + def __init__(self) -> None: self.root = None - def left_rotate(self, node): + def left_rotate(self, node) -> None: # set the node as the left child node of the current node's right node right_node = node.right if right_node is None: return + # right node's left node become the right node of current node + node.right = right_node.left + if right_node.left is not None: + right_node.left.parent = node + right_node.parent = node.parent + # check the parent case + if node.parent is None: + self.root = right_node + elif node is node.parent.left: + node.parent.left = right_node else: - # right node's left node become the right node of current node - node.right = right_node.left - if right_node.left is not None: - right_node.left.parent = node - right_node.parent = node.parent - # check the parent case - if node.parent is None: - self.root = right_node - elif node is node.parent.left: - node.parent.left = right_node - else: - node.parent.right = right_node - right_node.left = node - node.parent = right_node + node.parent.right = right_node + right_node.left = node + node.parent = right_node - def right_rotate(self, node): + def right_rotate(self, node) -> None: # set the node as the right child node of the current node's left node left_node = node.left if left_node is None: return + # left node's right node become the left node of current node + node.left = left_node.right + if left_node.right is not None: + left_node.right.parent = node + left_node.parent = node.parent + # check the parent case + if node.parent is None: + self.root = left_node + elif node is node.parent.left: + node.parent.left = left_node else: - # left node's right node become the left node of current node - node.left = left_node.right - if left_node.right is not None: - left_node.right.parent = node - left_node.parent = node.parent - # check the parent case - if node.parent is None: - self.root = left_node - elif node is node.parent.left: - node.parent.left = left_node - else: - node.parent.right = left_node - left_node.right = node - node.parent = left_node + node.parent.right = left_node + left_node.right = node + node.parent = left_node - def insert(self, node): + def insert(self, node) -> None: # the inserted node's color is default is red root = self.root insert_node_parent = None @@ -82,7 +78,7 @@ def insert(self, node): # fix the tree to self.fix_insert(node) - def fix_insert(self, node): + def fix_insert(self, node) -> None: # case 1 the parent is null, then set the inserted node as root and color = 0 if node.parent is None: node.color = 0 @@ -102,7 +98,7 @@ def fix_insert(self, node): node.parent.parent.color = 1 node = node.parent.parent continue - elif node is node.parent.right: + if node is node.parent.right: # case 3.2 the uncle node is black or null, # and the node is right of parent # then set his parent node is current node @@ -125,7 +121,7 @@ def fix_insert(self, node): node.parent.parent.color = 1 node = node.parent.parent continue - elif node is node.parent.left: + if node is node.parent.left: # case 3.2 the uncle node is black or null, # and the node is right of parent # then set his parent node is current node @@ -139,12 +135,11 @@ def fix_insert(self, node): self.left_rotate(node.parent.parent) self.root.color = 0 - def transplant(self, node_u, node_v): - """ - replace u with v + def transplant(self, node_u, node_v) -> None: + """Replace u with v :param node_u: replaced node :param node_v: - :return: None + :return: None. """ if node_u.parent is None: self.root = node_v @@ -157,10 +152,9 @@ def transplant(self, node_u, node_v): node_v.parent = node_u.parent def maximum(self, node): - """ - find the max node when node regard as a root node + """Find the max node when node regard as a root node :param node: - :return: max node + :return: max node. """ temp_node = node while temp_node.right is not None: @@ -168,17 +162,16 @@ def maximum(self, node): return temp_node def minimum(self, node): - """ - find the minimum node when node regard as a root node + """Find the minimum node when node regard as a root node :param node: - :return: minimum node + :return: minimum node. """ temp_node = node while temp_node.left: temp_node = temp_node.left return temp_node - def delete(self, node): + def delete(self, node) -> None: # find the node position node_color = node.color if node.left is None: @@ -205,7 +198,7 @@ def delete(self, node): if node_color == 0: self.delete_fixup(temp_node) - def delete_fixup(self, node): + def delete_fixup(self, node) -> None: # 4 cases while node is not self.root and node.color == 0: # node is not root and color is black @@ -282,13 +275,3 @@ def inorder(self): res.append({"val": root.val, "color": root.color}) root = root.right return res - - -if __name__ == "__main__": - rb = RBTree() - children = [11, 2, 14, 1, 7, 15, 5, 8, 4] - for child in children: - node = RBNode(child, 1) - print(child) - rb.insert(node) - print(rb.inorder()) diff --git a/algorithms/data_structures/segment_tree.py b/algorithms/data_structures/segment_tree.py index ff0f433b3..cfddaf9bc 100644 --- a/algorithms/data_structures/segment_tree.py +++ b/algorithms/data_structures/segment_tree.py @@ -1,25 +1,24 @@ -""" -Segment_tree creates a segment tree with a given array and function, +"""Segment_tree creates a segment tree with a given array and function, allowing queries to be done later in log(N) time -function takes 2 values and returns a same type value +function takes 2 values and returns a same type value. """ class SegmentTree: - def __init__(self, arr, function): + def __init__(self, arr, function) -> None: self.segment = [0 for x in range(3 * len(arr) + 3)] self.arr = arr self.fn = function self.make_tree(0, 0, len(arr) - 1) - def make_tree(self, i, left, r): + def make_tree(self, i, left, r) -> None: if left == r: self.segment[i] = self.arr[left] elif left < r: self.make_tree(2 * i + 1, left, int((left + r) / 2)) self.make_tree(2 * i + 2, int((left + r) / 2) + 1, r) self.segment[i] = self.fn( - self.segment[2 * i + 1], self.segment[2 * i + 2] + self.segment[2 * i + 1], self.segment[2 * i + 2], ) def __query(self, i, low, high, left, r): @@ -29,9 +28,8 @@ def __query(self, i, low, high, left, r): return self.segment[i] val1 = self.__query(2 * i + 1, low, int((low + high) / 2), left, r) val2 = self.__query( - 2 * i + 2, int((low + high + 2) / 2), high, left, r + 2 * i + 2, int((low + high + 2) / 2), high, left, r, ) - print(low, high, " returned ", val1, val2) if val1 is not None: if val2 is not None: return self.fn(val1, val2) diff --git a/algorithms/data_structures/separate_chaining_hash_table.py b/algorithms/data_structures/separate_chaining_hash_table.py index 92efcdfd7..ac7508410 100644 --- a/algorithms/data_structures/separate_chaining_hash_table.py +++ b/algorithms/data_structures/separate_chaining_hash_table.py @@ -1,5 +1,4 @@ -""" -Separate Chaining Hash Table +"""Separate Chaining Hash Table. Hash table implementation using separate chaining (linked lists) for collision resolution. @@ -21,6 +20,7 @@ class _Node: key: The key stored in this node. value: The value stored in this node. next_node: Reference to the next node in the chain. + """ def __init__( @@ -47,6 +47,7 @@ class SeparateChainingHashTable: >>> del table['hello'] >>> table.get('hello') is None True + """ _empty = None @@ -56,6 +57,7 @@ def __init__(self, size: int = 11) -> None: Args: size: Number of buckets. + """ self.size = size self._len = 0 @@ -67,6 +69,7 @@ def put(self, key: object, value: object) -> None: Args: key: The key to insert. value: The value associated with the key. + """ hash_ = self.hash(key) node_ = self._table[hash_] @@ -89,6 +92,7 @@ def get(self, key: object) -> object | None: Returns: The associated value, or None if the key is not found. + """ hash_ = self.hash(key) node_ = self._table[hash_] @@ -103,6 +107,7 @@ def del_(self, key: object) -> None: Args: key: The key to delete. + """ hash_ = self.hash(key) node_ = self._table[hash_] @@ -125,6 +130,7 @@ def hash(self, key: object) -> int: Returns: Bucket index. + """ return hash(key) % self.size diff --git a/algorithms/data_structures/sqrt_decomposition.py b/algorithms/data_structures/sqrt_decomposition.py index 67ce4a80e..ed0647e0e 100644 --- a/algorithms/data_structures/sqrt_decomposition.py +++ b/algorithms/data_structures/sqrt_decomposition.py @@ -1,5 +1,4 @@ -""" -Square Root (Sqrt) Decomposition +"""Square Root (Sqrt) Decomposition. Divides an array into blocks of size √n to allow O(√n) range queries and point updates — a simple alternative to segment trees for range-aggregate @@ -40,6 +39,7 @@ class SqrtDecomposition: >>> sd.update(4, 10) >>> sd.query(0, 8) 50 + """ def __init__(self, arr: list[int | float]) -> None: @@ -47,6 +47,7 @@ def __init__(self, arr: list[int | float]) -> None: Args: arr: Input array of numbers. + """ self.data = list(arr) n = len(self.data) @@ -57,7 +58,7 @@ def __init__(self, arr: list[int | float]) -> None: for i, val in enumerate(self.data): self.blocks[i // self.block_size] += val - def update(self, index: int, value: int | float) -> None: + def update(self, index: int, value: float) -> None: """Set ``data[index]`` to *value* and update the block sum. Args: @@ -72,6 +73,7 @@ def update(self, index: int, value: int | float) -> None: >>> sd.update(1, 10) >>> sd.query(0, 2) 14 + """ if index < 0 or index >= len(self.data): msg = f"index {index} out of range for length {len(self.data)}" @@ -98,6 +100,7 @@ def query(self, left: int, right: int) -> int | float: >>> sd = SqrtDecomposition([1, 2, 3, 4, 5]) >>> sd.query(1, 3) 9 + """ if left < 0 or right >= len(self.data) or left > right: msg = f"invalid range [{left}, {right}] for length {len(self.data)}" diff --git a/algorithms/data_structures/stack.py b/algorithms/data_structures/stack.py index 28346c7eb..26167ec69 100644 --- a/algorithms/data_structures/stack.py +++ b/algorithms/data_structures/stack.py @@ -1,5 +1,4 @@ -""" -Stack Abstract Data Type +"""Stack Abstract Data Type. Implementations of the stack ADT using both a fixed-size array and a linked list. Both support push, pop, peek, is_empty, len, iter, and str. @@ -14,7 +13,10 @@ from __future__ import annotations from abc import ABCMeta, abstractmethod -from collections.abc import Iterator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator class AbstractStack(metaclass=ABCMeta): @@ -35,6 +37,7 @@ def is_empty(self) -> bool: Returns: True if the stack has no elements. + """ return self._top == -1 @@ -63,6 +66,7 @@ class ArrayStack(AbstractStack): >>> s.push(1) >>> s.pop() 1 + """ def __init__(self, size: int = 10) -> None: @@ -70,6 +74,7 @@ def __init__(self, size: int = 10) -> None: Args: size: Initial capacity of the underlying array. + """ super().__init__() self._array: list[object | None] = [None] * size @@ -87,6 +92,7 @@ def push(self, value: object) -> None: Args: value: The value to push. + """ self._top += 1 if self._top == len(self._array): @@ -101,9 +107,11 @@ def pop(self) -> object: Raises: IndexError: If the stack is empty. + """ if self.is_empty(): - raise IndexError("Stack is empty") + msg = "Stack is empty" + raise IndexError(msg) value = self._array[self._top] self._top -= 1 return value @@ -116,9 +124,11 @@ def peek(self) -> object: Raises: IndexError: If the stack is empty. + """ if self.is_empty(): - raise IndexError("Stack is empty") + msg = "Stack is empty" + raise IndexError(msg) return self._array[self._top] def _expand(self) -> None: @@ -142,6 +152,7 @@ class LinkedListStack(AbstractStack): >>> s.push(1) >>> s.pop() 1 + """ def __init__(self) -> None: @@ -161,6 +172,7 @@ def push(self, value: object) -> None: Args: value: The value to push. + """ node = StackNode(value) node.next = self.head @@ -175,9 +187,11 @@ def pop(self) -> object: Raises: IndexError: If the stack is empty. + """ if self.is_empty(): - raise IndexError("Stack is empty") + msg = "Stack is empty" + raise IndexError(msg) value = self.head.value self.head = self.head.next self._top -= 1 @@ -191,7 +205,9 @@ def peek(self) -> object: Raises: IndexError: If the stack is empty. + """ if self.is_empty(): - raise IndexError("Stack is empty") + msg = "Stack is empty" + raise IndexError(msg) return self.head.value diff --git a/algorithms/data_structures/trie.py b/algorithms/data_structures/trie.py index 2a6c04380..7597e800c 100644 --- a/algorithms/data_structures/trie.py +++ b/algorithms/data_structures/trie.py @@ -1,24 +1,24 @@ -""" -Implement a trie with insert, search, and startsWith methods. +"""Implement a trie with insert, search, and startsWith methods. Note: You may assume that all inputs are consist of lowercase letters a-z. + """ import collections class TrieNode: - def __init__(self): + def __init__(self) -> None: self.children = collections.defaultdict(TrieNode) self.is_word = False class Trie: - def __init__(self): + def __init__(self) -> None: self.root = TrieNode() - def insert(self, word): + def insert(self, word) -> None: current = self.root for letter in word: current = current.children[letter] @@ -32,7 +32,7 @@ def search(self, word): return False return current.is_word - def starts_with(self, prefix): + def starts_with(self, prefix) -> bool: current = self.root for letter in prefix: current = current.children.get(letter) diff --git a/algorithms/data_structures/union_find.py b/algorithms/data_structures/union_find.py index 4e73538fd..0ebd2fc83 100644 --- a/algorithms/data_structures/union_find.py +++ b/algorithms/data_structures/union_find.py @@ -1,5 +1,4 @@ -""" -Union-Find (Disjoint Set) Data Structure +"""Union-Find (Disjoint Set) Data Structure. A Union-Find data structure supporting add, find (root), and unite operations. Uses union by size and path compression for near-constant amortized time. @@ -28,6 +27,7 @@ class Union: True >>> uf.root(1) == uf.root(3) False + """ def __init__(self) -> None: @@ -40,6 +40,7 @@ def add(self, element: object) -> None: Args: element: The element to add. + """ self.parents[element] = element self.size[element] = 1 @@ -53,6 +54,7 @@ def root(self, element: object) -> object: Returns: The root representative of the element's set. + """ while element != self.parents[element]: self.parents[element] = self.parents[self.parents[element]] @@ -65,6 +67,7 @@ def unite(self, element1: object, element2: object) -> None: Args: element1: An element in the first set. element2: An element in the second set. + """ root1, root2 = self.root(element1), self.root(element2) if root1 == root2: diff --git a/algorithms/data_structures/veb_tree.py b/algorithms/data_structures/veb_tree.py index 57a286bfe..c8227a552 100644 --- a/algorithms/data_structures/veb_tree.py +++ b/algorithms/data_structures/veb_tree.py @@ -1,5 +1,4 @@ -""" -Van Emde Boas Tree (vEB Tree) / van Emde Boas priority queue +"""Van Emde Boas Tree (vEB Tree) / van Emde Boas priority queue. Reference: https://en.wikipedia.org/wiki/Van_Emde_Boas_tree @@ -18,8 +17,7 @@ class VEBTree: - """ - Van Emde Boas tree supporting fast predecessor/successor queries. + """Van Emde Boas tree supporting fast predecessor/successor queries. Attributes: u (int): Universe size (power of 2) @@ -27,11 +25,11 @@ class VEBTree: max (int | None): Maximum element in the tree summary (VEBTree | None): Summary tree cluster (list[VEBTree] | None): Array of clusters + """ - def __init__(self, universe_size): - """ - Initialize a Van Emde Boas tree. + def __init__(self, universe_size) -> None: + """Initialize a Van Emde Boas tree. Args: universe_size (int): Size of the universe; must be a power of 2 and > 0. @@ -39,13 +37,17 @@ def __init__(self, universe_size): Raises: TypeError: If universe_size is not an integer. ValueError: If universe_size <= 0 or not a power of 2. + """ if not isinstance(universe_size, int): - raise TypeError("universe_size must be an integer.") + msg = "universe_size must be an integer." + raise TypeError(msg) if not universe_size > 0: - raise ValueError("universe_size must be greater than 0.") - if not (universe_size & (universe_size - 1)) == 0: - raise ValueError("universe_size must be a power of 2.") + msg = "universe_size must be greater than 0." + raise ValueError(msg) + if universe_size & universe_size - 1 != 0: + msg = "universe_size must be a power of 2." + raise ValueError(msg) self.u = universe_size self.min = None @@ -61,46 +63,46 @@ def __init__(self, universe_size): self.summary = VEBTree(self.upper_sqrt) self.cluster = [VEBTree(self.lower_sqrt) for _ in range(self.upper_sqrt)] - def _validate_key(self, x): - """ - Check if x is within the universe range. + def _validate_key(self, x) -> None: + """Check if x is within the universe range. Args: x (int): Element to validate. Raises: ValueError: If x is not in the range [0, u-1]. + """ if not (0 <= x < self.u): - raise ValueError(f"Key {x} out of universe range [0, {self.u - 1}]") + msg = f"Key {x} out of universe range [0, {self.u - 1}]" + raise ValueError(msg) def high(self, x): - """ - Return the high part (cluster index) of element x. + """Return the high part (cluster index) of element x. Args: x (int): Element to split. Returns: int: Cluster index corresponding to x. + """ return x // self.lower_sqrt def low(self, x): - """ - Return the low part (position within cluster) of element x. + """Return the low part (position within cluster) of element x. Args: x (int): Element to split. Returns: int: Position within cluster corresponding to x. + """ return x % self.lower_sqrt def index(self, high, low): - """ - Combine high and low parts to get original element. + """Combine high and low parts to get original element. Args: high (int): Cluster index. @@ -108,27 +110,28 @@ def index(self, high, low): Returns: int: Original element corresponding to high and low. + """ return high * self.lower_sqrt + low - def empty_insert(self, x): - """ - Insert x into an empty vEB tree (sets min and max). + def empty_insert(self, x) -> None: + """Insert x into an empty vEB tree (sets min and max). Args: x (int): Element to insert. + """ self.min = self.max = x - def insert(self, x): - """ - Insert an element into the Van Emde Boas tree. + def insert(self, x) -> None: + """Insert an element into the Van Emde Boas tree. Args: x (int): Element to insert; must be in the universe [0, u-1]. Raises: ValueError: If x is outside the universe. + """ self._validate_key(x) if self.min is None: @@ -148,12 +151,10 @@ def insert(self, x): else: self.cluster[high].insert(low) - if x > self.max: - self.max = x + self.max = max(self.max, x) def member(self, x): - """ - Check whether element x exists in the tree. + """Check whether element x exists in the tree. Args: x (int): Element to check. @@ -163,18 +164,17 @@ def member(self, x): Raises: ValueError: If x is outside the universe. + """ self._validate_key(x) - if x == self.min or x == self.max: + if x in (self.min, self.max): return True - elif self.u == 2: + if self.u == 2: return False - else: - return self.cluster[self.high(x)].member(self.low(x)) + return self.cluster[self.high(x)].member(self.low(x)) def successor(self, x): - """ - Return the smallest element greater than x in the tree. + """Return the smallest element greater than x in the tree. Args: x (int): Element to find successor for. @@ -184,6 +184,7 @@ def successor(self, x): Raises: ValueError: If x is outside the universe. + """ self._validate_key(x) if self.u == 2: @@ -202,22 +203,21 @@ def successor(self, x): if max_low is not None and low < max_low: offset = self.cluster[high].successor(low) return self.index(high, offset) - else: - succ_cluster = self.summary.successor(high) - if succ_cluster is None: - return None - offset = self.cluster[succ_cluster].min - return self.index(succ_cluster, offset) + succ_cluster = self.summary.successor(high) + if succ_cluster is None: + return None + offset = self.cluster[succ_cluster].min + return self.index(succ_cluster, offset) - def delete(self, x): - """ - Remove element x from the Van Emde Boas tree. + def delete(self, x) -> None: + """Remove element x from the Van Emde Boas tree. Args: x (int): Element to delete. Raises: ValueError: If x is outside the universe. + """ self._validate_key(x) if self.min == self.max: @@ -254,19 +254,19 @@ def delete(self, x): self.max = self.index(high, self.cluster[high].max) def minimum(self): - """ - Get the minimum element in the tree. + """Get the minimum element in the tree. Returns: int | None: Minimum element, or None if tree is empty. + """ return self.min def maximum(self): - """ - Get the maximum element in the tree. + """Get the maximum element in the tree. Returns: int | None: Maximum element, or None if tree is empty. + """ return self.max diff --git a/algorithms/dynamic_programming/__init__.py b/algorithms/dynamic_programming/__init__.py index 729aeb130..f0fdd9caa 100644 --- a/algorithms/dynamic_programming/__init__.py +++ b/algorithms/dynamic_programming/__init__.py @@ -1,5 +1,4 @@ -""" -Dynamic Programming Algorithms +"""Dynamic Programming Algorithms. A collection of dynamic programming algorithm implementations. """ @@ -37,25 +36,35 @@ from .word_break import word_break __all__ = [ - # buy_sell_stock - "max_profit_naive", - "max_profit_optimized", + # knapsack + "Item", + # job_scheduling + "Job", # climbing_stairs "climb_stairs", "climb_stairs_optimized", - # coin_change - "count", + "combination_sum_bottom_up", # combination_sum "combination_sum_topdown", - "combination_sum_bottom_up", + # coin_change + "count", + # count_paths_dp + "count_paths_dp", + "count_paths_memo", + "count_paths_recursive", + # rod_cut + "cut_rod", # edit_distance "edit_distance", # egg_drop "egg_drop", + "fib_iter", + "fib_list", # fib "fib_recursive", - "fib_list", - "fib_iter", + # k_factor + "find_k_factor", + "get_maximum_value", # hosoya_triangle "hosoya", "hosoya_testing", @@ -63,14 +72,7 @@ "house_robber", # int_divide "int_divide", - # job_scheduling - "Job", - "schedule", - # k_factor - "find_k_factor", - # knapsack - "Item", - "get_maximum_value", + "is_match", # longest_common_subsequence "longest_common_subsequence", # longest_increasing @@ -81,7 +83,9 @@ "matrix_chain_order", # max_product_subarray "max_product", - "subarray_with_max_product", + # buy_sell_stock + "max_profit_naive", + "max_profit_optimized", # max_subarray "max_subarray", # min_cost_path @@ -93,15 +97,10 @@ "planting_trees", # regex_matching "regex_matching", - "is_match", - # rod_cut - "cut_rod", - # word_break - "word_break", + "schedule", + "subarray_with_max_product", # bitmask "tsp", - # count_paths_dp - "count_paths_dp", - "count_paths_memo", - "count_paths_recursive", + # word_break + "word_break", ] diff --git a/algorithms/dynamic_programming/buy_sell_stock.py b/algorithms/dynamic_programming/buy_sell_stock.py index 4bf15c4f9..884aa6ff7 100644 --- a/algorithms/dynamic_programming/buy_sell_stock.py +++ b/algorithms/dynamic_programming/buy_sell_stock.py @@ -1,5 +1,4 @@ -""" -Best Time to Buy and Sell Stock +"""Best Time to Buy and Sell Stock. Given an array of stock prices, find the maximum profit from a single buy-sell transaction (buy before sell). @@ -32,9 +31,10 @@ def max_profit_naive(prices: list[int]) -> int: 5 >>> max_profit_naive([7, 6, 4, 3, 1]) 0 + """ max_so_far = 0 - for i in range(0, len(prices) - 1): + for i in range(len(prices) - 1): for j in range(i + 1, len(prices)): max_so_far = max(max_so_far, prices[j] - prices[i]) return max_so_far @@ -54,6 +54,7 @@ def max_profit_optimized(prices: list[int]) -> int: 5 >>> max_profit_optimized([7, 6, 4, 3, 1]) 0 + """ cur_max, max_so_far = 0, 0 for i in range(1, len(prices)): diff --git a/algorithms/dynamic_programming/climbing_stairs.py b/algorithms/dynamic_programming/climbing_stairs.py index d69bcb376..33806f763 100644 --- a/algorithms/dynamic_programming/climbing_stairs.py +++ b/algorithms/dynamic_programming/climbing_stairs.py @@ -1,5 +1,4 @@ -""" -Climbing Stairs +"""Climbing Stairs. Count the number of distinct ways to climb a staircase of n steps, where each move is either 1 or 2 steps. @@ -32,6 +31,7 @@ def climb_stairs(steps: int) -> int: 2 >>> climb_stairs(10) 89 + """ arr = [1, 1] for _ in range(1, steps): @@ -53,6 +53,7 @@ def climb_stairs_optimized(steps: int) -> int: 2 >>> climb_stairs_optimized(10) 89 + """ a_steps = b_steps = 1 for _ in range(steps): diff --git a/algorithms/dynamic_programming/coin_change.py b/algorithms/dynamic_programming/coin_change.py index 588870179..ba209cf96 100644 --- a/algorithms/dynamic_programming/coin_change.py +++ b/algorithms/dynamic_programming/coin_change.py @@ -1,5 +1,4 @@ -""" -Coin Change (Number of Ways) +"""Coin Change (Number of Ways). Given a value and a set of coin denominations, count how many distinct combinations of coins sum to the given value. @@ -29,6 +28,7 @@ def count(coins: list[int], value: int) -> int: 4 >>> count([2, 5, 3, 6], 10) 5 + """ dp_array = [1] + [0] * value diff --git a/algorithms/dynamic_programming/combination_sum.py b/algorithms/dynamic_programming/combination_sum.py index aaa466820..baf42539f 100644 --- a/algorithms/dynamic_programming/combination_sum.py +++ b/algorithms/dynamic_programming/combination_sum.py @@ -1,5 +1,4 @@ -""" -Combination Sum IV +"""Combination Sum IV. Given an array of distinct positive integers and a target, find the number of possible combinations (order matters) that add up to the target. @@ -28,6 +27,7 @@ def _helper_topdown(nums: list[int], target: int, dp: list[int]) -> int: Returns: Number of combinations that sum to target. + """ if dp[target] != -1: return dp[target] @@ -52,6 +52,7 @@ def combination_sum_topdown(nums: list[int], target: int) -> int: Examples: >>> combination_sum_topdown([1, 2, 3], 4) 7 + """ dp = [-1] * (target + 1) dp[0] = 1 @@ -71,10 +72,11 @@ def combination_sum_bottom_up(nums: list[int], target: int) -> int: Examples: >>> combination_sum_bottom_up([1, 2, 3], 4) 7 + """ combs = [0] * (target + 1) combs[0] = 1 - for i in range(0, len(combs)): + for i in range(len(combs)): for num in nums: if i - num >= 0: combs[i] += combs[i - num] diff --git a/algorithms/dynamic_programming/edit_distance.py b/algorithms/dynamic_programming/edit_distance.py index 83931cbfb..a20c7182c 100644 --- a/algorithms/dynamic_programming/edit_distance.py +++ b/algorithms/dynamic_programming/edit_distance.py @@ -1,5 +1,4 @@ -""" -Edit Distance (Levenshtein Distance) +"""Edit Distance (Levenshtein Distance). Find the minimum number of insertions, deletions, and substitutions required to transform one word into another. @@ -29,6 +28,7 @@ def edit_distance(word_a: str, word_b: str) -> int: 4 >>> edit_distance('horse', 'ros') 3 + """ length_a, length_b = len(word_a) + 1, len(word_b) + 1 diff --git a/algorithms/dynamic_programming/egg_drop.py b/algorithms/dynamic_programming/egg_drop.py index 8b9f9b58a..430eebbd3 100644 --- a/algorithms/dynamic_programming/egg_drop.py +++ b/algorithms/dynamic_programming/egg_drop.py @@ -1,5 +1,4 @@ -""" -Egg Drop Problem +"""Egg Drop Problem. Given K eggs and a building with N floors, determine the minimum number of moves needed to find the critical floor F in the worst case. @@ -31,6 +30,7 @@ def egg_drop(n: int, k: int) -> int: 2 >>> egg_drop(2, 6) 3 + """ egg_floor = [[0 for _ in range(k + 1)] for _ in range(n + 1)] @@ -46,7 +46,6 @@ def egg_drop(n: int, k: int) -> int: egg_floor[i][j] = _INT_MAX for x in range(1, j + 1): res = 1 + max(egg_floor[i - 1][x - 1], egg_floor[i][j - x]) - if res < egg_floor[i][j]: - egg_floor[i][j] = res + egg_floor[i][j] = min(egg_floor[i][j], res) return egg_floor[n][k] diff --git a/algorithms/dynamic_programming/fib.py b/algorithms/dynamic_programming/fib.py index eaffab3a8..0ec7a2117 100644 --- a/algorithms/dynamic_programming/fib.py +++ b/algorithms/dynamic_programming/fib.py @@ -1,5 +1,4 @@ -""" -Fibonacci Number +"""Fibonacci Number. Compute the n-th Fibonacci number using three different approaches: recursive, list-based DP, and iterative. @@ -33,6 +32,7 @@ def fib_recursive(n: int) -> int: Examples: >>> fib_recursive(10) 55 + """ assert n >= 0, "n must be a positive integer" @@ -53,6 +53,7 @@ def fib_list(n: int) -> int: Examples: >>> fib_list(10) 55 + """ assert n >= 0, "n must be a positive integer" @@ -74,6 +75,7 @@ def fib_iter(n: int) -> int: Examples: >>> fib_iter(10) 55 + """ assert n >= 0, "n must be positive integer" diff --git a/algorithms/dynamic_programming/hosoya_triangle.py b/algorithms/dynamic_programming/hosoya_triangle.py index 3f4a4b18c..babeadf39 100644 --- a/algorithms/dynamic_programming/hosoya_triangle.py +++ b/algorithms/dynamic_programming/hosoya_triangle.py @@ -1,5 +1,4 @@ -""" -Hosoya Triangle +"""Hosoya Triangle. The Hosoya triangle (originally Fibonacci triangle) is a triangular arrangement of numbers where each entry is the sum of two entries above it. @@ -27,6 +26,7 @@ def hosoya(height: int, width: int) -> int: Examples: >>> hosoya(4, 2) 4 + """ if (width == 0) and (height in (0, 1)): return 1 @@ -51,6 +51,7 @@ def hosoya_testing(height: int) -> list[int]: Examples: >>> hosoya_testing(1) [1] + """ res = [] for i in range(height): diff --git a/algorithms/dynamic_programming/house_robber.py b/algorithms/dynamic_programming/house_robber.py index 6809ab98e..bea2e955b 100644 --- a/algorithms/dynamic_programming/house_robber.py +++ b/algorithms/dynamic_programming/house_robber.py @@ -1,5 +1,4 @@ -""" -House Robber +"""House Robber. Determine the maximum amount of money that can be robbed from a row of houses without robbing two adjacent houses. @@ -26,6 +25,7 @@ def house_robber(houses: list[int]) -> int: Examples: >>> house_robber([1, 2, 16, 3, 15, 3, 12, 1]) 44 + """ last, now = 0, 0 for house in houses: diff --git a/algorithms/dynamic_programming/int_divide.py b/algorithms/dynamic_programming/int_divide.py index 10e182f0a..791ca9bae 100644 --- a/algorithms/dynamic_programming/int_divide.py +++ b/algorithms/dynamic_programming/int_divide.py @@ -1,5 +1,4 @@ -""" -Integer Partition +"""Integer Partition. Count the number of ways a positive integer can be represented as a sum of positive integers (order does not matter). @@ -28,6 +27,7 @@ def int_divide(decompose: int) -> int: 5 >>> int_divide(7) 15 + """ arr = [[0 for i in range(decompose + 1)] for j in range(decompose + 1)] arr[1][1] = 1 diff --git a/algorithms/dynamic_programming/job_scheduling.py b/algorithms/dynamic_programming/job_scheduling.py index c28e934ac..fd402cada 100644 --- a/algorithms/dynamic_programming/job_scheduling.py +++ b/algorithms/dynamic_programming/job_scheduling.py @@ -1,5 +1,4 @@ -""" -Weighted Job Scheduling +"""Weighted Job Scheduling. Given a set of jobs with start times, finish times, and profits, find the maximum profit subset such that no two jobs overlap. @@ -32,6 +31,7 @@ def _binary_search(job: list[Job], start_index: int) -> int: Returns: Index of the latest compatible job, or -1 if none exists. + """ left = 0 right = start_index - 1 @@ -60,6 +60,7 @@ def schedule(job: list[Job]) -> int: Examples: >>> schedule([Job(1, 3, 2), Job(2, 3, 4)]) 4 + """ job = sorted(job, key=lambda j: j.finish) diff --git a/algorithms/dynamic_programming/k_factor.py b/algorithms/dynamic_programming/k_factor.py index 3eb55f0a6..ccf5f3fde 100644 --- a/algorithms/dynamic_programming/k_factor.py +++ b/algorithms/dynamic_programming/k_factor.py @@ -1,5 +1,4 @@ -""" -K-Factor of a String +"""K-Factor of a String. The K factor of a string is the number of times 'abba' appears as a substring. Given a length and a k_factor, count the number of strings of @@ -30,6 +29,7 @@ def find_k_factor(length: int, k_factor: int) -> int: 1 >>> find_k_factor(7, 1) 70302 + """ mat = [ [[0 for i in range(4)] for j in range((length - 1) // 3 + 2)] diff --git a/algorithms/dynamic_programming/knapsack.py b/algorithms/dynamic_programming/knapsack.py index 5c786f82f..99c75eb40 100644 --- a/algorithms/dynamic_programming/knapsack.py +++ b/algorithms/dynamic_programming/knapsack.py @@ -1,5 +1,4 @@ -""" -0/1 Knapsack Problem +"""0/1 Knapsack Problem. Given items with values and weights, and a knapsack capacity, find the maximum total value that fits in the knapsack. @@ -35,11 +34,12 @@ def get_maximum_value(items: list[Item], capacity: int) -> int: Examples: >>> get_maximum_value([Item(60, 5), Item(50, 3), Item(70, 4), Item(30, 2)], 5) 80 + """ dp = [0] * (capacity + 1) for item in items: for cur_weight in reversed(range(item.weight, capacity + 1)): dp[cur_weight] = max( - dp[cur_weight], item.value + dp[cur_weight - item.weight] + dp[cur_weight], item.value + dp[cur_weight - item.weight], ) return dp[capacity] diff --git a/algorithms/dynamic_programming/longest_common_subsequence.py b/algorithms/dynamic_programming/longest_common_subsequence.py index 8c0969f76..be3673669 100644 --- a/algorithms/dynamic_programming/longest_common_subsequence.py +++ b/algorithms/dynamic_programming/longest_common_subsequence.py @@ -1,5 +1,4 @@ -""" -Longest Common Subsequence +"""Longest Common Subsequence. Find the length of the longest subsequence common to two strings. @@ -26,6 +25,7 @@ def longest_common_subsequence(s_1: str, s_2: str) -> int: Examples: >>> longest_common_subsequence('abcdgh', 'aedfhr') 3 + """ m = len(s_1) n = len(s_2) diff --git a/algorithms/dynamic_programming/longest_increasing.py b/algorithms/dynamic_programming/longest_increasing.py index 44793b2a5..5e233ebe8 100644 --- a/algorithms/dynamic_programming/longest_increasing.py +++ b/algorithms/dynamic_programming/longest_increasing.py @@ -1,5 +1,4 @@ -""" -Longest Increasing Subsequence +"""Longest Increasing Subsequence. Find the length of the longest strictly increasing subsequence in an array. @@ -32,11 +31,12 @@ def longest_increasing_subsequence(sequence: list[int]) -> int: Examples: >>> longest_increasing_subsequence([10, 9, 2, 5, 3, 7, 101, 18]) 4 + """ length = len(sequence) counts = [1 for _ in range(length)] for i in range(1, length): - for j in range(0, i): + for j in range(i): if sequence[i] > sequence[j]: counts[i] = max(counts[i], counts[j] + 1) return max(counts) @@ -54,6 +54,7 @@ def longest_increasing_subsequence_optimized(sequence: list[int]) -> int: Examples: >>> longest_increasing_subsequence_optimized([10, 9, 2, 5, 3, 7, 101, 18]) 4 + """ max_val = max(sequence) tree = [0] * (max_val << 2) @@ -100,6 +101,7 @@ def longest_increasing_subsequence_optimized2(sequence: list[int]) -> int: Examples: >>> longest_increasing_subsequence_optimized2([10, 9, 2, 5, 3, 7, 101, 18]) 4 + """ length = len(sequence) tree = [0] * (length << 2) diff --git a/algorithms/dynamic_programming/matrix_chain_order.py b/algorithms/dynamic_programming/matrix_chain_order.py index c581b6e4c..1762a0b3e 100644 --- a/algorithms/dynamic_programming/matrix_chain_order.py +++ b/algorithms/dynamic_programming/matrix_chain_order.py @@ -1,5 +1,4 @@ -""" -Matrix Chain Multiplication +"""Matrix Chain Multiplication. Find the optimal parenthesization of a chain of matrices to minimize the total number of scalar multiplications. @@ -32,6 +31,7 @@ def matrix_chain_order(array: list[int]) -> tuple[list[list[int]], list[list[int >>> m, s = matrix_chain_order([30, 35, 15, 5, 10, 20, 25]) >>> m[1][6] 15125 + """ n = len(array) matrix = [[0 for x in range(n)] for x in range(n)] diff --git a/algorithms/dynamic_programming/max_product_subarray.py b/algorithms/dynamic_programming/max_product_subarray.py index 78837810e..9838422e8 100644 --- a/algorithms/dynamic_programming/max_product_subarray.py +++ b/algorithms/dynamic_programming/max_product_subarray.py @@ -1,5 +1,4 @@ -""" -Maximum Product Subarray +"""Maximum Product Subarray. Find the contiguous subarray within an array that has the largest product. @@ -31,13 +30,14 @@ def max_product(nums: list[int]) -> int: Examples: >>> max_product([2, 3, -2, 4]) 6 + """ lmin = lmax = gmax = nums[0] for num in nums[1:]: t_1 = num * lmax t_2 = num * lmin - lmax = max(max(t_1, t_2), num) - lmin = min(min(t_1, t_2), num) + lmax = max(t_1, t_2, num) + lmin = min(t_1, t_2, num) gmax = max(gmax, lmax) return gmax @@ -55,6 +55,7 @@ def subarray_with_max_product(arr: list[int]) -> tuple[int, list[int]]: Examples: >>> subarray_with_max_product([-2, -3, 6, 0, -7, -5]) (36, [-2, -3, 6]) + """ length = len(arr) product_so_far = max_product_end = 1 diff --git a/algorithms/dynamic_programming/max_subarray.py b/algorithms/dynamic_programming/max_subarray.py index 9ec978ba4..0337ed5d5 100644 --- a/algorithms/dynamic_programming/max_subarray.py +++ b/algorithms/dynamic_programming/max_subarray.py @@ -1,5 +1,4 @@ -""" -Maximum Subarray (Kadane's Algorithm) +"""Maximum Subarray (Kadane's Algorithm). Find the contiguous subarray with the largest sum. @@ -25,6 +24,7 @@ def max_subarray(array: list[int]) -> int: Examples: >>> max_subarray([1, 2, -3, 4, 5, -7, 23]) 25 + """ max_so_far = max_now = array[0] for i in range(1, len(array)): diff --git a/algorithms/dynamic_programming/min_cost_path.py b/algorithms/dynamic_programming/min_cost_path.py index 3da2c2361..7faff8901 100644 --- a/algorithms/dynamic_programming/min_cost_path.py +++ b/algorithms/dynamic_programming/min_cost_path.py @@ -1,5 +1,4 @@ -""" -Minimum Cost Path +"""Minimum Cost Path. Find the minimum cost to travel from station 0 to station N-1 given a cost matrix where cost[i][j] is the price of going from station i @@ -31,6 +30,7 @@ def min_cost(cost: list[list[int]]) -> int: >>> min_cost([[0, 15, 80, 90], [-1, 0, 40, 50], ... [-1, -1, 0, 70], [-1, -1, -1, 0]]) 65 + """ length = len(cost) dist = [_INF] * length diff --git a/algorithms/dynamic_programming/num_decodings.py b/algorithms/dynamic_programming/num_decodings.py index 7efa71467..f8b194a0e 100644 --- a/algorithms/dynamic_programming/num_decodings.py +++ b/algorithms/dynamic_programming/num_decodings.py @@ -1,5 +1,4 @@ -""" -Decode Ways +"""Decode Ways. Given an encoded message of digits, count the total number of ways to decode it where 'A' = 1, 'B' = 2, ..., 'Z' = 26. @@ -28,6 +27,7 @@ def num_decodings(enc_mes: str) -> int: 2 >>> num_decodings("226") 3 + """ if not enc_mes or enc_mes[0] == "0": return 0 @@ -58,6 +58,7 @@ def num_decodings2(enc_mes: str) -> int: 2 >>> num_decodings2("226") 3 + """ if not enc_mes or enc_mes.startswith("0"): return 0 diff --git a/algorithms/dynamic_programming/planting_trees.py b/algorithms/dynamic_programming/planting_trees.py index 03a03f7f9..46b9d2bc3 100644 --- a/algorithms/dynamic_programming/planting_trees.py +++ b/algorithms/dynamic_programming/planting_trees.py @@ -1,5 +1,4 @@ -""" -Planting Trees +"""Planting Trees. Given an even number of trees along one side of a road, calculate the minimum total distance to move them into valid positions on both sides @@ -31,8 +30,9 @@ def planting_trees(trees: list[int], length: int, width: int) -> float: Examples: >>> planting_trees([0, 1, 10, 10], 10, 1) 2.414213562373095 + """ - trees = [0] + trees + trees = [0, *trees] n_pairs = int(len(trees) / 2) @@ -43,11 +43,11 @@ def planting_trees(trees: list[int], length: int, width: int) -> float: cmatrix = [[0 for _ in range(n_pairs + 1)] for _ in range(n_pairs + 1)] for r_i in range(1, n_pairs + 1): cmatrix[r_i][0] = cmatrix[r_i - 1][0] + sqrt( - width + abs(trees[r_i] - target_locations[r_i - 1]) ** 2 + width + abs(trees[r_i] - target_locations[r_i - 1]) ** 2, ) for l_i in range(1, n_pairs + 1): cmatrix[0][l_i] = cmatrix[0][l_i - 1] + abs( - trees[l_i] - target_locations[l_i - 1] + trees[l_i] - target_locations[l_i - 1], ) for r_i in range(1, n_pairs + 1): diff --git a/algorithms/dynamic_programming/regex_matching.py b/algorithms/dynamic_programming/regex_matching.py index b29f59c72..8d0cb3755 100644 --- a/algorithms/dynamic_programming/regex_matching.py +++ b/algorithms/dynamic_programming/regex_matching.py @@ -1,5 +1,4 @@ -""" -Regular Expression Matching +"""Regular Expression Matching. Implement regular expression matching with support for '.' (matches any single character) and '*' (matches zero or more of the preceding element). @@ -29,6 +28,7 @@ def is_match(str_a: str, str_b: str) -> bool: False >>> is_match("aa", "a*") True + """ len_a, len_b = len(str_a) + 1, len(str_b) + 1 matches = [[False] * len_b for _ in range(len_a)] diff --git a/algorithms/dynamic_programming/rod_cut.py b/algorithms/dynamic_programming/rod_cut.py index e113d79de..1de8f9ac7 100644 --- a/algorithms/dynamic_programming/rod_cut.py +++ b/algorithms/dynamic_programming/rod_cut.py @@ -1,5 +1,4 @@ -""" -Rod Cutting Problem +"""Rod Cutting Problem. Given a rod of length n and a list of prices for each piece length, determine the maximum revenue obtainable by cutting and selling the pieces. @@ -28,6 +27,7 @@ def cut_rod(price: list[int]) -> int: Examples: >>> cut_rod([1, 5, 8, 9, 10, 17, 17, 20]) 22 + """ n = len(price) val = [0] * (n + 1) diff --git a/algorithms/dynamic_programming/word_break.py b/algorithms/dynamic_programming/word_break.py index 61dba9861..1b16181a2 100644 --- a/algorithms/dynamic_programming/word_break.py +++ b/algorithms/dynamic_programming/word_break.py @@ -1,5 +1,4 @@ -""" -Word Break +"""Word Break. Given a string and a dictionary of words, determine whether the string can be segmented into a sequence of dictionary words. @@ -29,11 +28,12 @@ def word_break(word: str, word_dict: set[str]) -> bool: True >>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"}) False + """ dp_array = [False] * (len(word) + 1) dp_array[0] = True for i in range(1, len(word) + 1): - for j in range(0, i): + for j in range(i): if dp_array[j] and word[j:i] in word_dict: dp_array[i] = True break diff --git a/algorithms/graph/__init__.py b/algorithms/graph/__init__.py index 8c76d111f..698e1834a 100644 --- a/algorithms/graph/__init__.py +++ b/algorithms/graph/__init__.py @@ -1,12 +1,10 @@ -""" -Collection of graph algorithms. -""" +"""Collection of graph algorithms.""" from __future__ import annotations from algorithms.data_structures.graph import DirectedEdge, DirectedGraph, Node from algorithms.graph.a_star import a_star -from algorithms.graph.all_factors import ( +from algorithms.math.all_factors import ( get_factors, get_factors_iterative1, get_factors_iterative2, @@ -43,7 +41,7 @@ from algorithms.graph.prims_minimum_spanning import prims_minimum_spanning from algorithms.graph.satisfiability import solve_sat from algorithms.graph.shortest_distance_from_all_buildings import shortest_distance -from algorithms.graph.sudoku_solver import Sudoku +from algorithms.matrix.sudoku_solver import Sudoku from algorithms.graph.tarjan import Tarjan from algorithms.graph.topological_sort_bfs import topological_sort from algorithms.graph.topological_sort_dfs import top_sort, top_sort_recursive @@ -56,84 +54,84 @@ from algorithms.graph.word_ladder import ladder_length __all__ = [ + # dijkstra + "Dijkstra", + # graph + "DirectedEdge", + "DirectedGraph", + # minimum_spanning_tree + "DisjointSet", + "Edge", + # kahns_algorithm + "KahnsSolution", + "Node", + # sudoku_solver + "Sudoku", + # tarjan + "Tarjan", + # clone_graph + "UndirectedGraphNode", # a_star "a_star", # all_pairs_shortest_path "all_pairs_shortest_path", # bellman_ford "bellman_ford", + # traversal + "bfs_traverse", # check_bipartite "check_bipartite", - # clone_graph - "UndirectedGraphNode", "clone_graph", "clone_graph1", "clone_graph2", - # dijkstra - "Dijkstra", + # count_islands (bfs) + "count_islands", + "dfs_traverse", + "dfs_traverse_recursive", "dijkstra", - # find_all_cliques - "find_all_cliques", - # graph - "DirectedEdge", - "DirectedGraph", - "Node", - # kahns_algorithm - "KahnsSolution", - # markov_chain - "iterating_markov_chain", - "next_state", # maximum_flow "dinic", "edmonds_karp", + # find_all_cliques + "find_all_cliques", + # maze_search (dfs) + "find_path_dfs", "ford_fulkerson", + # all_factors + "get_factors", + "get_factors_iterative1", + "get_factors_iterative2", + # markov_chain + "iterating_markov_chain", + "kruskal", + # word_ladder + "ladder_length", + # blossom + "max_matching", # maximum_flow_bfs "maximum_flow_bfs", # maximum_flow_dfs "maximum_flow_dfs", - # minimum_spanning_tree - "DisjointSet", - "Edge", - "kruskal", - # prims_minimum_spanning - "prims_minimum_spanning", - # satisfiability - "solve_sat", - # tarjan - "Tarjan", - # traversal - "bfs_traverse", - "dfs_traverse", - "dfs_traverse_recursive", - # count_islands (bfs) - "count_islands", - # count_islands (dfs) - "num_islands_dfs", # maze_search (bfs) "maze_search", - # maze_search (dfs) - "find_path_dfs", - # word_ladder - "ladder_length", + "next_state", + # count_islands (dfs) + "num_islands_dfs", + # count_islands_unionfind + "num_islands_unionfind", + # pacific_atlantic + "pacific_atlantic", + # prims_minimum_spanning + "prims_minimum_spanning", # shortest_distance_from_all_buildings "shortest_distance", + # satisfiability + "solve_sat", + # topological_sort_dfs + "top_sort", + "top_sort_recursive", # topological_sort (bfs) "topological_sort", - # all_factors - "get_factors", - "get_factors_iterative1", - "get_factors_iterative2", - # pacific_atlantic - "pacific_atlantic", - # sudoku_solver - "Sudoku", # walls_and_gates "walls_and_gates", - # topological_sort_dfs - "top_sort", - "top_sort_recursive", - # count_islands_unionfind - "num_islands_unionfind", - # blossom - "max_matching", ] diff --git a/algorithms/graph/a_star.py b/algorithms/graph/a_star.py index 2548486dd..cdb527dea 100644 --- a/algorithms/graph/a_star.py +++ b/algorithms/graph/a_star.py @@ -1,5 +1,4 @@ -""" -A* (A-star) Search Algorithm +"""A* (A-star) Search Algorithm. Finds the shortest path in a weighted graph using a heuristic function. @@ -13,8 +12,10 @@ from __future__ import annotations import heapq -from collections.abc import Callable -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Callable def a_star( @@ -38,13 +39,14 @@ def a_star( >>> g = {'A': [('B', 1)], 'B': [('C', 2)], 'C': []} >>> a_star(g, 'A', 'C', lambda n: 0) (['A', 'B', 'C'], 3) + """ open_set: list[tuple[float, float, Any, list[Any]]] = [] heapq.heappush(open_set, (h(start), 0, start, [start])) visited: set[Any] = set() while open_set: - f_score, g_score, current, path = heapq.heappop(open_set) + _f_score, g_score, current, path = heapq.heappop(open_set) if current == goal: return path, g_score @@ -56,6 +58,6 @@ def a_star( if neighbor not in visited: g = g_score + cost f = g + h(neighbor) - heapq.heappush(open_set, (f, g, neighbor, path + [neighbor])) + heapq.heappush(open_set, (f, g, neighbor, [*path, neighbor])) return None, float("inf") diff --git a/algorithms/graph/all_factors.py b/algorithms/graph/all_factors.py index 960e714f4..48ac9e3b0 100644 --- a/algorithms/graph/all_factors.py +++ b/algorithms/graph/all_factors.py @@ -1,100 +1,7 @@ -""" -Factor Combinations +"""Backward-compatible re-export; canonical location is algorithms.math.all_factors.""" -Given an integer n, return all possible combinations of its factors -(excluding 1 and n itself in the factorisation). - -Reference: https://leetcode.com/problems/factor-combinations/ - -Complexity: - Time: O(n^(1/2) * log n) (approximate, depends on factor density) - Space: O(log n) -""" - -from __future__ import annotations - - -def get_factors(n: int) -> list[list[int]]: - """Return all factor combinations of *n* using recursion. - - Args: - n: The number to factorise. - - Returns: - List of factor lists. - - Examples: - >>> get_factors(12) - [[2, 6], [2, 2, 3], [3, 4]] - """ - - def _factor( - n: int, - i: int, - combi: list[int], - res: list[list[int]], - ) -> list[list[int]]: - while i * i <= n: - if n % i == 0: - res += (combi + [i, int(n / i)],) - _factor(n / i, i, combi + [i], res) - i += 1 - return res - - return _factor(n, 2, [], []) - - -def get_factors_iterative1(n: int) -> list[list[int]]: - """Return all factor combinations using an explicit stack. - - Args: - n: The number to factorise. - - Returns: - List of factor lists. - - Examples: - >>> get_factors_iterative1(12) - [[2, 6], [3, 4], [2, 2, 3]] - """ - todo: list[tuple[int, int, list[int]]] = [(n, 2, [])] - res: list[list[int]] = [] - while todo: - n, i, combi = todo.pop() - while i * i <= n: - if n % i == 0: - res += (combi + [i, n // i],) - todo.append((n // i, i, combi + [i])) - i += 1 - return res - - -def get_factors_iterative2(n: int) -> list[list[int]]: - """Return all factor combinations using a stack-based approach. - - Args: - n: The number to factorise. - - Returns: - List of factor lists. - - Examples: - >>> get_factors_iterative2(12) - [[2, 2, 3], [2, 6], [3, 4]] - """ - ans: list[list[int]] = [] - stack: list[int] = [] - x = 2 - while True: - if x > n // x: - if not stack: - return ans - ans.append(stack + [n]) - x = stack.pop() - n *= x - x += 1 - elif n % x == 0: - stack.append(x) - n //= x - else: - x += 1 +from algorithms.math.all_factors import ( # noqa: F401 + get_factors, + get_factors_iterative1, + get_factors_iterative2, +) diff --git a/algorithms/graph/all_pairs_shortest_path.py b/algorithms/graph/all_pairs_shortest_path.py index 9154c914c..e60509ec7 100644 --- a/algorithms/graph/all_pairs_shortest_path.py +++ b/algorithms/graph/all_pairs_shortest_path.py @@ -1,5 +1,4 @@ -""" -All-Pairs Shortest Path (Floyd-Warshall) +"""All-Pairs Shortest Path (Floyd-Warshall). Given an n*n adjacency matrix, computes the shortest path between every pair of vertices using the Floyd-Warshall algorithm. @@ -33,6 +32,7 @@ def all_pairs_shortest_path( ... [[0, 1, float('inf')], [float('inf'), 0, 1], ... [1, float('inf'), 0]]) [[0, 1, 2], [2, 0, 1], [1, 2, 0]] + """ new_array = copy.deepcopy(adjacency_matrix) @@ -40,7 +40,8 @@ def all_pairs_shortest_path( for k in range(size): for i in range(size): for j in range(size): - if new_array[i][j] > new_array[i][k] + new_array[k][j]: - new_array[i][j] = new_array[i][k] + new_array[k][j] + new_array[i][j] = min( + new_array[i][j], new_array[i][k] + new_array[k][j], + ) return new_array diff --git a/algorithms/graph/bellman_ford.py b/algorithms/graph/bellman_ford.py index f4ed551f6..ebabe316a 100644 --- a/algorithms/graph/bellman_ford.py +++ b/algorithms/graph/bellman_ford.py @@ -1,5 +1,4 @@ -""" -Bellman-Ford Algorithm for Single-Source Shortest Path +"""Bellman-Ford Algorithm for Single-Source Shortest Path. Finds the shortest paths from a source vertex to all other vertices in a weighted directed graph. Unlike Dijkstra's algorithm it can handle graphs @@ -31,6 +30,7 @@ def bellman_ford(graph: dict[str, dict[str, float]], source: str) -> bool: >>> g = {'a': {'b': 1}, 'b': {'c': 2}, 'c': {}} >>> bellman_ford(g, 'a') True + """ distance: dict[str, float] = {} predecessor: dict[str, str | None] = {} @@ -39,16 +39,14 @@ def bellman_ford(graph: dict[str, dict[str, float]], source: str) -> bool: num_vertices = len(graph) for _ in range(1, num_vertices): - for current_node in graph: - for neighbor in graph[current_node]: - edge_weight = graph[current_node][neighbor] + for current_node, neighbors in graph.items(): + for neighbor, edge_weight in neighbors.items(): if distance[neighbor] > distance[current_node] + edge_weight: distance[neighbor] = distance[current_node] + edge_weight predecessor[neighbor] = current_node - for current_node in graph: - for neighbor in graph[current_node]: - edge_weight = graph[current_node][neighbor] + for current_node, neighbors in graph.items(): + for neighbor, edge_weight in neighbors.items(): if distance[neighbor] > distance[current_node] + edge_weight: return False @@ -68,6 +66,7 @@ def _initialize_single_source( source: The source vertex. distance: Dictionary to store shortest distances (modified in place). predecessor: Dictionary to store path predecessors (modified in place). + """ all_nodes: set[str] = set(graph.keys()) for neighbors in graph.values(): diff --git a/algorithms/graph/check_bipartite.py b/algorithms/graph/check_bipartite.py index 161b11935..ef27d6892 100644 --- a/algorithms/graph/check_bipartite.py +++ b/algorithms/graph/check_bipartite.py @@ -1,5 +1,4 @@ -""" -Check Bipartite Graph +"""Check Bipartite Graph. Determine whether an undirected graph is bipartite using BFS colouring. @@ -28,6 +27,7 @@ def check_bipartite(adj_list: list[list[int]]) -> bool: Examples: >>> check_bipartite([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) True + """ vertices = len(adj_list) diff --git a/algorithms/graph/check_digraph_strongly_connected.py b/algorithms/graph/check_digraph_strongly_connected.py index 2569517cc..2ce15cbfe 100644 --- a/algorithms/graph/check_digraph_strongly_connected.py +++ b/algorithms/graph/check_digraph_strongly_connected.py @@ -1,5 +1,4 @@ -""" -Check if a Directed Graph is Strongly Connected +"""Check if a Directed Graph is Strongly Connected. A directed graph is strongly connected if every vertex is reachable from every other vertex. This implementation uses two DFS passes (one on the @@ -25,6 +24,7 @@ def __init__(self, vertex_count: int) -> None: Args: vertex_count: Number of vertices (labelled 0 .. vertex_count-1). + """ self.vertex_count = vertex_count self.graph: dict[int, list[int]] = defaultdict(list) @@ -35,6 +35,7 @@ def add_edge(self, source: int, target: int) -> None: Args: source: Source vertex. target: Target vertex. + """ self.graph[source].append(target) @@ -50,6 +51,7 @@ def _dfs_util(self, source: int, visited: list[bool]) -> None: Args: source: Current vertex. visited: Visited flags (modified in place). + """ visited[source] = True for adjacent in self.graph[source]: @@ -61,6 +63,7 @@ def reverse_graph(self) -> Graph: Returns: A new Graph instance with reversed edges. + """ reverse = Graph(self.vertex_count) for source, adjacent in self.graph.items(): @@ -73,6 +76,7 @@ def is_strongly_connected(self) -> bool: Returns: True when every vertex can reach every other vertex. + """ if self.dfs(): reversed_graph = self.reverse_graph() diff --git a/algorithms/graph/clone_graph.py b/algorithms/graph/clone_graph.py index 906eb5433..568e45921 100644 --- a/algorithms/graph/clone_graph.py +++ b/algorithms/graph/clone_graph.py @@ -1,5 +1,4 @@ -""" -Clone an Undirected Graph +"""Clone an Undirected Graph. Each node contains a label and a list of its neighbours. Three strategies are provided: BFS-based, iterative DFS, and recursive DFS. @@ -28,6 +27,7 @@ def shallow_copy(self) -> UndirectedGraphNode: Returns: A new node with the same label. + """ return UndirectedGraphNode(self.label) @@ -36,6 +36,7 @@ def add_neighbor(self, node: UndirectedGraphNode) -> None: Args: node: Neighbour to add. + """ self.neighbors.append(node) @@ -48,6 +49,7 @@ def clone_graph1(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None Returns: The corresponding node in the cloned graph, or None. + """ if not node: return None @@ -75,6 +77,7 @@ def clone_graph2(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None Returns: The corresponding node in the cloned graph, or None. + """ if not node: return None @@ -102,6 +105,7 @@ def clone_graph(node: UndirectedGraphNode | None) -> UndirectedGraphNode | None: Returns: The corresponding node in the cloned graph, or None. + """ if not node: return None @@ -120,6 +124,7 @@ def _dfs( Args: node: Current node being cloned. dic: Mapping from original nodes to their clones. + """ for neighbor in node.neighbors: if neighbor not in dic: diff --git a/algorithms/graph/count_connected_number_of_component.py b/algorithms/graph/count_connected_number_of_component.py index 46652dfd5..b791f0eab 100644 --- a/algorithms/graph/count_connected_number_of_component.py +++ b/algorithms/graph/count_connected_number_of_component.py @@ -1,5 +1,4 @@ -""" -Count Connected Components in an Undirected Graph +"""Count Connected Components in an Undirected Graph. Uses DFS to count the number of connected components. @@ -27,6 +26,7 @@ def count_components(adjacency_list: list[list[int]], size: int) -> int: Examples: >>> count_components([[], [2], [1]], 2) 1 + """ count = 0 visited = [False] * (size + 1) @@ -48,6 +48,7 @@ def _dfs( source: Starting vertex. visited: Visited flags (modified in place). adjacency_list: Graph adjacency list. + """ visited[source] = True for child in adjacency_list[source]: diff --git a/algorithms/graph/count_islands_bfs.py b/algorithms/graph/count_islands_bfs.py index 2ef8d23ff..2c86b2001 100644 --- a/algorithms/graph/count_islands_bfs.py +++ b/algorithms/graph/count_islands_bfs.py @@ -1,5 +1,4 @@ -""" -Count Islands (BFS) +"""Count Islands (BFS). Given a 2D grid of 1s (land) and 0s (water), count the number of islands using breadth-first search. An island is a group of adjacent lands @@ -29,6 +28,7 @@ def count_islands(grid: list[list[int]]) -> int: Examples: >>> count_islands([[1, 0], [0, 1]]) 2 + """ row = len(grid) col = len(grid[0]) diff --git a/algorithms/graph/count_islands_dfs.py b/algorithms/graph/count_islands_dfs.py index c5310f862..fd009df3c 100644 --- a/algorithms/graph/count_islands_dfs.py +++ b/algorithms/graph/count_islands_dfs.py @@ -1,5 +1,4 @@ -""" -Count Islands (DFS) +"""Count Islands (DFS). Given a 2D grid of 1s (land) and 0s (water), count the number of islands using depth-first search. @@ -26,6 +25,7 @@ def num_islands(grid: list[list[int]]) -> int: Examples: >>> num_islands([[1, 0], [0, 1]]) 2 + """ count = 0 for i in range(len(grid)): @@ -43,6 +43,7 @@ def _dfs(grid: list[list[int]], i: int, j: int) -> None: grid: The grid (modified in place). i: Row index. j: Column index. + """ if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]): return diff --git a/algorithms/graph/count_islands_unionfind.py b/algorithms/graph/count_islands_unionfind.py index e053ef493..1f8a12d62 100644 --- a/algorithms/graph/count_islands_unionfind.py +++ b/algorithms/graph/count_islands_unionfind.py @@ -1,5 +1,4 @@ -""" -Count Islands via Union-Find +"""Count Islands via Union-Find. Uses the Union-Find (Disjoint Set) data structure to solve the "Number of Islands" problem. After each addLand operation, counts distinct connected @@ -33,6 +32,7 @@ def num_islands(positions: list[list[int]]) -> list[int]: Examples: >>> num_islands([[0, 0], [0, 1], [1, 2], [2, 1]]) [1, 1, 2, 3] + """ result: list[int] = [] islands = Union() diff --git a/algorithms/graph/cycle_detection.py b/algorithms/graph/cycle_detection.py index 3c7ea0484..48063aa0b 100644 --- a/algorithms/graph/cycle_detection.py +++ b/algorithms/graph/cycle_detection.py @@ -1,5 +1,4 @@ -""" -Cycle Detection in a Directed Graph +"""Cycle Detection in a Directed Graph. Uses DFS with three-colour marking to determine whether a directed graph contains a cycle. @@ -38,6 +37,7 @@ def is_in_cycle( Returns: True if a cycle is detected through *vertex*. + """ if traversal_states[vertex] == TraversalState.GRAY: return True @@ -63,11 +63,12 @@ def contains_cycle(graph: dict[str, list[str]]) -> bool: True >>> contains_cycle({'A': ['B'], 'B': []}) False + """ - traversal_states = {vertex: TraversalState.WHITE for vertex in graph} + traversal_states = dict.fromkeys(graph, TraversalState.WHITE) for vertex, state in traversal_states.items(): if state == TraversalState.WHITE and is_in_cycle( - graph, traversal_states, vertex + graph, traversal_states, vertex, ): return True return False diff --git a/algorithms/graph/dijkstra.py b/algorithms/graph/dijkstra.py index c47fee724..3fb5606fb 100644 --- a/algorithms/graph/dijkstra.py +++ b/algorithms/graph/dijkstra.py @@ -1,5 +1,4 @@ -""" -Dijkstra's Single-Source Shortest-Path Algorithm +"""Dijkstra's Single-Source Shortest-Path Algorithm. Finds shortest distances from a source vertex to every other vertex in a graph with non-negative edge weights. @@ -22,6 +21,7 @@ def __init__(self, vertex_count: int) -> None: Args: vertex_count: Number of vertices. + """ self.vertex_count = vertex_count self.graph: list[list[int]] = [ @@ -37,6 +37,7 @@ def min_distance(self, dist: list[float], min_dist_set: list[bool]) -> int: Returns: Index of the closest unvisited vertex. + """ min_dist = float("inf") min_index = 0 @@ -62,6 +63,7 @@ def dijkstra(self, src: int) -> list[float]: >>> g.graph = [[0, 1, 4], [1, 0, 2], [4, 2, 0]] >>> g.dijkstra(0) [0, 1, 3] + """ dist: list[float] = [float("inf")] * self.vertex_count dist[src] = 0 @@ -74,7 +76,8 @@ def dijkstra(self, src: int) -> list[float]: for target in range(self.vertex_count): if self.graph[source][target] <= 0 or min_dist_set[target]: continue - if dist[target] > dist[source] + self.graph[source][target]: - dist[target] = dist[source] + self.graph[source][target] + dist[target] = min( + dist[target], dist[source] + self.graph[source][target], + ) return dist diff --git a/algorithms/graph/dijkstra_heapq.py b/algorithms/graph/dijkstra_heapq.py index 34e05fed4..b118ccdbb 100644 --- a/algorithms/graph/dijkstra_heapq.py +++ b/algorithms/graph/dijkstra_heapq.py @@ -1,5 +1,4 @@ -""" -Dijkstra's Shortest-Path Algorithm (Heap-Optimised) +"""Dijkstra's Shortest-Path Algorithm (Heap-Optimised). Computes single-source shortest paths in a graph with non-negative edge weights using a min-heap (priority queue) for efficient vertex selection. @@ -52,10 +51,11 @@ def dijkstra( ... } >>> dijkstra(g, "s", "t") (8, ['s', 'b', 'd', 't']) + """ dist: dict[str, int | float] = {v: float("inf") for v in graph} dist[source] = 0 - prev: dict[str, str | None] = {v: None for v in graph} + prev: dict[str, str | None] = dict.fromkeys(graph) heap: list[tuple[int | float, str]] = [(0, source)] while heap: @@ -73,7 +73,7 @@ def dijkstra( # Reconstruct path path: list[str] = [] - node: str | None = target if target else None + node: str | None = target or None if node and dist.get(node, float("inf")) < float("inf"): while node is not None: path.append(node) diff --git a/algorithms/graph/find_all_cliques.py b/algorithms/graph/find_all_cliques.py index 17bbb51ef..1232cae76 100644 --- a/algorithms/graph/find_all_cliques.py +++ b/algorithms/graph/find_all_cliques.py @@ -1,5 +1,4 @@ -""" -Find All Cliques (Bron-Kerbosch) +"""Find All Cliques (Bron-Kerbosch). Finds every maximal clique in an undirected graph. @@ -26,6 +25,7 @@ def find_all_cliques(edges: dict[str, set[str]]) -> list[list[str]]: Examples: >>> find_all_cliques({'0': {'1'}, '1': {'0'}}) [['0', '1']] + """ compsub: list[str] = [] solutions: list[list[str]] = [] diff --git a/algorithms/graph/find_path.py b/algorithms/graph/find_path.py index d6150b8cc..900eec72f 100644 --- a/algorithms/graph/find_path.py +++ b/algorithms/graph/find_path.py @@ -1,5 +1,4 @@ -""" -Find Paths in a Graph +"""Find Paths in a Graph. Provides functions to find a single path, all paths, or the shortest path between two nodes using recursion and backtracking. @@ -34,18 +33,18 @@ def find_path( Examples: >>> find_path({'A': ['B'], 'B': ['C'], 'C': []}, 'A', 'C') ['A', 'B', 'C'] + """ if path is None: path = [] - path = path + [start] + path = [*path, start] if start == end: return path if start not in graph: return None for node in graph[start]: if node not in path: - newpath = find_path(graph, node, end, path) - return newpath + return find_path(graph, node, end, path) return None @@ -69,10 +68,11 @@ def find_all_path( Examples: >>> find_all_path({'A': ['B', 'C'], 'B': ['C'], 'C': []}, 'A', 'C') [['A', 'B', 'C'], ['A', 'C']] + """ if path is None: path = [] - path = path + [start] + path = [*path, start] if start == end: return [path] if start not in graph: @@ -106,10 +106,11 @@ def find_shortest_path( Examples: >>> find_shortest_path({'A': ['B', 'C'], 'B': ['C'], 'C': []}, 'A', 'C') ['A', 'C'] + """ if path is None: path = [] - path = path + [start] + path = [*path, start] if start == end: return path if start not in graph: diff --git a/algorithms/graph/kahns_algorithm.py b/algorithms/graph/kahns_algorithm.py index db116c293..7b8f8f82d 100644 --- a/algorithms/graph/kahns_algorithm.py +++ b/algorithms/graph/kahns_algorithm.py @@ -1,5 +1,4 @@ -""" -Kahn's Algorithm (Topological Sort via BFS) +"""Kahn's Algorithm (Topological Sort via BFS). Computes a topological ordering of a directed acyclic graph using an in-degree based BFS approach. @@ -20,7 +19,7 @@ class Solution: """Wrapper class for Kahn's topological sort.""" def topological_sort( - self, vertices: int, adj: list[list[int]] + self, vertices: int, adj: list[list[int]], ) -> list[int]: """Return a topological ordering of the graph. @@ -35,6 +34,7 @@ def topological_sort( Examples: >>> Solution().topological_sort(3, [[1], [2], []]) [0, 1, 2] + """ in_degree = [0] * vertices for i in range(vertices): @@ -42,7 +42,7 @@ def topological_sort( in_degree[neighbor] += 1 queue = deque( - [i for i in range(vertices) if in_degree[i] == 0] + [i for i in range(vertices) if in_degree[i] == 0], ) topo_order: list[int] = [] diff --git a/algorithms/graph/markov_chain.py b/algorithms/graph/markov_chain.py index 356d659f6..b7826ac3b 100644 --- a/algorithms/graph/markov_chain.py +++ b/algorithms/graph/markov_chain.py @@ -1,5 +1,4 @@ -""" -Markov Chain +"""Markov Chain. Provides utilities for stepping through and iterating a discrete Markov chain described as a dictionary of transition probabilities. @@ -14,8 +13,10 @@ from __future__ import annotations import random -from collections.abc import Iterator -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Iterator def _choose_state(state_map: dict[Any, float]) -> Any | None: @@ -26,6 +27,7 @@ def _choose_state(state_map: dict[Any, float]) -> Any | None: Returns: The selected state, or None if probabilities don't sum to 1. + """ choice = random.random() probability_reached = 0.0 @@ -50,6 +52,7 @@ def next_state(chain: dict[Any, dict[Any, float]], current_state: Any) -> Any: >>> c = {'A': {'A': 1.0}} >>> next_state(c, 'A') 'A' + """ next_state_map = chain.get(current_state) return _choose_state(next_state_map) @@ -67,6 +70,7 @@ def iterating_markov_chain( Yields: Successive states of the chain. + """ while True: state = next_state(chain, state) diff --git a/algorithms/graph/maximum_flow.py b/algorithms/graph/maximum_flow.py index aafd3aed6..e47ab3dd7 100644 --- a/algorithms/graph/maximum_flow.py +++ b/algorithms/graph/maximum_flow.py @@ -1,5 +1,4 @@ -""" -Maximum Flow Algorithms +"""Maximum Flow Algorithms. Implements Ford-Fulkerson (DFS), Edmonds-Karp (BFS) and Dinic's algorithm for computing maximum flow in a flow network. @@ -39,6 +38,7 @@ def _dfs( Returns: Flow pushed along the augmenting path found. + """ if idx == sink: return current_flow @@ -68,6 +68,7 @@ def ford_fulkerson(capacity: list[list[int]], source: int, sink: int) -> int: Examples: >>> ford_fulkerson([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 + """ vertices = len(capacity) ret = 0 @@ -96,6 +97,7 @@ def edmonds_karp(capacity: list[list[int]], source: int, sink: int) -> int: Examples: >>> edmonds_karp([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 + """ vertices = len(capacity) ret = 0 @@ -118,7 +120,7 @@ def edmonds_karp(capacity: list[list[int]], source: int, sink: int) -> int: visit[nxt] = True par[nxt] = idx queue.put( - (nxt, min(current_flow, capacity[idx][nxt] - flow[idx][nxt])) + (nxt, min(current_flow, capacity[idx][nxt] - flow[idx][nxt])), ) if par[sink] == -1: break @@ -151,6 +153,7 @@ def _dinic_bfs( Returns: True if sink is reachable from source. + """ vertices = len(capacity) queue: Queue[int] = Queue() @@ -187,6 +190,7 @@ def _dinic_dfs( Returns: Flow pushed. + """ if idx == sink: return current_flow @@ -218,6 +222,7 @@ def dinic(capacity: list[list[int]], source: int, sink: int) -> int: Examples: >>> dinic([[0, 10, 0], [0, 0, 10], [0, 0, 0]], 0, 2) 10 + """ vertices = len(capacity) flow = [[0] * vertices for _ in range(vertices)] diff --git a/algorithms/graph/maximum_flow_bfs.py b/algorithms/graph/maximum_flow_bfs.py index 3b990a7fa..473317d46 100644 --- a/algorithms/graph/maximum_flow_bfs.py +++ b/algorithms/graph/maximum_flow_bfs.py @@ -1,5 +1,4 @@ -""" -Maximum Flow via BFS +"""Maximum Flow via BFS. Computes maximum flow in a network represented as an adjacency matrix, using BFS to find augmenting paths. @@ -32,6 +31,7 @@ def maximum_flow_bfs(adjacency_matrix: list[list[int]]) -> int: Examples: >>> maximum_flow_bfs([[0, 10, 0], [0, 0, 10], [0, 0, 0]]) 10 + """ new_array = copy.deepcopy(adjacency_matrix) total = 0 @@ -60,8 +60,7 @@ def maximum_flow_bfs(adjacency_matrix: list[list[int]]) -> int: tmp = len(new_array) - 1 while tmp != 0: - if min_flow > new_array[path[tmp]][tmp]: - min_flow = new_array[path[tmp]][tmp] + min_flow = min(min_flow, new_array[path[tmp]][tmp]) tmp = path[tmp] tmp = len(new_array) - 1 diff --git a/algorithms/graph/maximum_flow_dfs.py b/algorithms/graph/maximum_flow_dfs.py index 5da16d88c..e25055c12 100644 --- a/algorithms/graph/maximum_flow_dfs.py +++ b/algorithms/graph/maximum_flow_dfs.py @@ -1,5 +1,4 @@ -""" -Maximum Flow via DFS +"""Maximum Flow via DFS. Computes maximum flow in a network represented as an adjacency matrix, using DFS to find augmenting paths. @@ -31,6 +30,7 @@ def maximum_flow_dfs(adjacency_matrix: list[list[int]]) -> int: Examples: >>> maximum_flow_dfs([[0, 10, 0], [0, 0, 10], [0, 0, 0]]) 10 + """ new_array = copy.deepcopy(adjacency_matrix) total = 0 @@ -59,8 +59,7 @@ def maximum_flow_dfs(adjacency_matrix: list[list[int]]) -> int: tmp = len(new_array) - 1 while tmp != 0: - if min_flow > new_array[path[tmp]][tmp]: - min_flow = new_array[path[tmp]][tmp] + min_flow = min(min_flow, new_array[path[tmp]][tmp]) tmp = path[tmp] tmp = len(new_array) - 1 diff --git a/algorithms/graph/maze_search_bfs.py b/algorithms/graph/maze_search_bfs.py index dc2d5739d..313bfedab 100644 --- a/algorithms/graph/maze_search_bfs.py +++ b/algorithms/graph/maze_search_bfs.py @@ -1,5 +1,4 @@ -""" -Maze Search (BFS) +"""Maze Search (BFS). Find the minimum number of steps from the top-left corner to the bottom-right corner of a grid. Only cells with value 1 may be traversed. @@ -29,6 +28,7 @@ def maze_search(maze: list[list[int]]) -> int: 2 >>> maze_search([[1, 0], [0, 1]]) -1 + """ blocked, allowed = 0, 1 unvisited, visited = 0, 1 diff --git a/algorithms/graph/maze_search_dfs.py b/algorithms/graph/maze_search_dfs.py index 49f7b4309..24acba01a 100644 --- a/algorithms/graph/maze_search_dfs.py +++ b/algorithms/graph/maze_search_dfs.py @@ -1,5 +1,4 @@ -""" -Maze Search (DFS) +"""Maze Search (DFS). Find the shortest path from the top-left corner to the bottom-right corner of a grid using depth-first search with backtracking. Only cells with @@ -25,9 +24,9 @@ def find_path(maze: list[list[int]]) -> int: Examples: >>> find_path([[1, 1], [1, 1]]) 2 + """ - cnt = _dfs(maze, 0, 0, 0, -1) - return cnt + return _dfs(maze, 0, 0, 0, -1) def _dfs( @@ -48,6 +47,7 @@ def _dfs( Returns: Updated best path length. + """ directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] @@ -55,11 +55,8 @@ def _dfs( col = len(maze[0]) if i == row - 1 and j == col - 1: - if cnt == -1: + if cnt == -1 or cnt > depth: cnt = depth - else: - if cnt > depth: - cnt = depth return cnt maze[i][j] = 0 diff --git a/algorithms/graph/minimum_spanning_tree.py b/algorithms/graph/minimum_spanning_tree.py index 87a01dd7d..444c71991 100644 --- a/algorithms/graph/minimum_spanning_tree.py +++ b/algorithms/graph/minimum_spanning_tree.py @@ -1,5 +1,4 @@ -""" -Minimum Spanning Tree (Kruskal's Algorithm) +"""Minimum Spanning Tree (Kruskal's Algorithm). Finds the MST of an undirected graph using Kruskal's algorithm with a disjoint-set (union-find) data structure. @@ -31,6 +30,7 @@ def __init__(self, size: int) -> None: Args: size: Number of elements. + """ self.parent = list(range(size)) self.size = [1] * size @@ -41,6 +41,7 @@ def merge_set(self, node1: int, node2: int) -> None: Args: node1: First element. node2: Second element. + """ node1 = self.find_set(node1) node2 = self.find_set(node2) @@ -60,6 +61,7 @@ def find_set(self, node: int) -> int: Returns: Root representative. + """ if self.parent[node] != node: self.parent[node] = self.find_set(self.parent[node]) @@ -81,6 +83,7 @@ def kruskal(vertex_count: int, edges: list[Edge], forest: DisjointSet) -> int: >>> e = [Edge(0, 1, 1), Edge(1, 2, 2), Edge(0, 2, 3)] >>> kruskal(3, e, DisjointSet(3)) 3 + """ edges.sort(key=lambda edge: edge.weight) diff --git a/algorithms/graph/pacific_atlantic.py b/algorithms/graph/pacific_atlantic.py index ebe81d54e..d624d51d9 100644 --- a/algorithms/graph/pacific_atlantic.py +++ b/algorithms/graph/pacific_atlantic.py @@ -1,5 +1,4 @@ -""" -Pacific Atlantic Water Flow +"""Pacific Atlantic Water Flow. Given an m*n matrix of heights, find all cells from which water can flow to both the Pacific (top / left edges) and Atlantic (bottom / right edges) @@ -27,6 +26,7 @@ def pacific_atlantic(matrix: list[list[int]]) -> list[list[int]]: Examples: >>> pacific_atlantic([[1]]) [[0, 0]] + """ n = len(matrix) if not n: @@ -65,6 +65,7 @@ def _dfs( height: Previous cell height. i: Row index. j: Column index. + """ if i < 0 or i >= len(matrix) or j < 0 or j >= len(matrix[0]): return diff --git a/algorithms/graph/path_between_two_vertices_in_digraph.py b/algorithms/graph/path_between_two_vertices_in_digraph.py index edd5d0c88..65366f5be 100644 --- a/algorithms/graph/path_between_two_vertices_in_digraph.py +++ b/algorithms/graph/path_between_two_vertices_in_digraph.py @@ -1,5 +1,4 @@ -""" -Path Between Two Vertices in a Directed Graph +"""Path Between Two Vertices in a Directed Graph. Determines whether there is a directed path from a source vertex to a target vertex using DFS. @@ -22,6 +21,7 @@ def __init__(self, vertex_count: int) -> None: Args: vertex_count: Number of vertices. + """ self.vertex_count = vertex_count self.graph: dict[int, list[int]] = defaultdict(list) @@ -33,6 +33,7 @@ def add_edge(self, source: int, target: int) -> None: Args: source: Source vertex. target: Target vertex. + """ self.graph[source].append(target) @@ -42,6 +43,7 @@ def _dfs(self, source: int, target: int) -> None: Args: source: Source vertex. target: Target vertex. + """ visited = [False] * self.vertex_count self._dfs_util(visited, source, target) @@ -53,6 +55,7 @@ def _dfs_util(self, visited: list[bool], source: int, target: int) -> None: visited: Visited flags. source: Current vertex. target: Destination vertex. + """ visited[source] = True for i in self.graph[source]: @@ -75,6 +78,7 @@ def is_reachable(self, source: int, target: int) -> bool: Examples: >>> g = Graph(2); g.add_edge(0, 1); g.is_reachable(0, 1) True + """ self.has_path = False self._dfs(source, target) diff --git a/algorithms/graph/prims_minimum_spanning.py b/algorithms/graph/prims_minimum_spanning.py index 158f7f223..f142d1499 100644 --- a/algorithms/graph/prims_minimum_spanning.py +++ b/algorithms/graph/prims_minimum_spanning.py @@ -1,5 +1,4 @@ -""" -Prim's Minimum Spanning Tree +"""Prim's Minimum Spanning Tree. Computes the weight of a minimum spanning tree for a connected weighted undirected graph using a priority queue. @@ -31,6 +30,7 @@ def prims_minimum_spanning( Examples: >>> prims_minimum_spanning({1: [[1, 2]], 2: [[1, 1]]}) 1 + """ vis: list[Any] = [] heap: list[list[int | Any]] = [[0, 1]] diff --git a/algorithms/graph/satisfiability.py b/algorithms/graph/satisfiability.py index 147771c1f..ec9071037 100644 --- a/algorithms/graph/satisfiability.py +++ b/algorithms/graph/satisfiability.py @@ -1,5 +1,4 @@ -""" -2-SAT Satisfiability +"""2-SAT Satisfiability. Given a formula in conjunctive normal form (2-CNF), finds an assignment of True/False values that satisfies all clauses, or reports that no solution @@ -30,6 +29,7 @@ def _dfs_transposed( graph: Transposed graph adjacency list. order: Finish order (appended to). visited: Visited flags. + """ visited[vertex] = True for adjacent in graph[vertex]: @@ -53,6 +53,7 @@ def _dfs( vertex_scc: SCC mapping (modified in place). graph: Graph adjacency list. visited: Visited flags. + """ visited[vertex] = True vertex_scc[vertex] = current_comp @@ -68,6 +69,7 @@ def _add_edge(graph: dict[Any, list[Any]], vertex_from: Any, vertex_to: Any) -> graph: Adjacency list (modified in place). vertex_from: Source vertex. vertex_to: Target vertex. + """ if vertex_from not in graph: graph[vertex_from] = [] @@ -82,9 +84,10 @@ def _scc(graph: dict[Any, list[Any]]) -> dict[Any, int]: Returns: Mapping from vertex to its SCC index. + """ order: list[Any] = [] - visited = {vertex: False for vertex in graph} + visited = dict.fromkeys(graph, False) graph_transposed: dict[Any, list[Any]] = {vertex: [] for vertex in graph} @@ -96,7 +99,7 @@ def _scc(graph: dict[Any, list[Any]]) -> dict[Any, int]: if not visited[vertex]: _dfs_transposed(vertex, graph_transposed, order, visited) - visited = {vertex: False for vertex in graph} + visited = dict.fromkeys(graph, False) vertex_scc: dict[Any, int] = {} current_comp = 0 @@ -119,6 +122,7 @@ def _build_graph( Returns: Implication graph as an adjacency list. + """ graph: dict[tuple[str, bool], list[tuple[str, bool]]] = {} @@ -149,6 +153,7 @@ def solve_sat( Examples: >>> solve_sat([(('x', False), ('y', False)), (('x', True), ('y', True))]) {'x': True, 'y': False} + """ graph = _build_graph(formula) vertex_scc = _scc(graph) @@ -172,6 +177,5 @@ def solve_sat( lit, neg = comp_repr[comp] comp_value[vertex_scc[(lit, not neg)]] = True - value = {var: comp_value[vertex_scc[(var, False)]] for var, _ in graph} + return {var: comp_value[vertex_scc[(var, False)]] for var, _ in graph} - return value diff --git a/algorithms/graph/shortest_distance_from_all_buildings.py b/algorithms/graph/shortest_distance_from_all_buildings.py index 3185afd91..8bfe72a9f 100644 --- a/algorithms/graph/shortest_distance_from_all_buildings.py +++ b/algorithms/graph/shortest_distance_from_all_buildings.py @@ -1,5 +1,4 @@ -""" -Shortest Distance from All Buildings +"""Shortest Distance from All Buildings. Given a 2D grid with buildings (1), empty land (0) and obstacles (2), find the empty land with the smallest total distance to all buildings. @@ -28,6 +27,7 @@ def shortest_distance(grid: list[list[int]]) -> int: Examples: >>> shortest_distance([[1, 0, 1]]) 2 + """ if not grid or not grid[0]: return -1 @@ -65,6 +65,7 @@ def _bfs( i: Row of the building. j: Column of the building. count: Number of buildings visited so far. + """ q: deque[tuple[int, int, int]] = deque([(i, j, 0)]) while q: diff --git a/algorithms/graph/strongly_connected_components_kosaraju.py b/algorithms/graph/strongly_connected_components_kosaraju.py index e7a5e1e06..1c449408f 100644 --- a/algorithms/graph/strongly_connected_components_kosaraju.py +++ b/algorithms/graph/strongly_connected_components_kosaraju.py @@ -1,5 +1,4 @@ -""" -Strongly Connected Components (Kosaraju's Algorithm) +"""Strongly Connected Components (Kosaraju's Algorithm). Counts the number of strongly connected components in a directed graph using two DFS passes. @@ -33,6 +32,7 @@ def dfs( adj: Adjacency list. visited: Visited flags (-1 = unvisited). stk: Stack recording finish order. + """ visited[i] = 1 @@ -55,6 +55,7 @@ def kosaraju(self, vertices: int, adj: list[list[int]]) -> int: Examples: >>> Kosaraju().kosaraju(3, [[1], [2], [0]]) 1 + """ stk: list[int] = [] visited = [-1] * (vertices + 1) diff --git a/algorithms/graph/sudoku_solver.py b/algorithms/graph/sudoku_solver.py index 7e18af6f9..a6d825df6 100644 --- a/algorithms/graph/sudoku_solver.py +++ b/algorithms/graph/sudoku_solver.py @@ -1,142 +1,3 @@ -""" -Sudoku Solver (DFS / Backtracking) +"""Backward-compatible re-export; canonical location is algorithms.matrix.sudoku_solver.""" -Solves a Sudoku puzzle using constraint propagation and depth-first search -with backtracking, starting from the cell with the fewest possible values. - -Reference: https://leetcode.com/problems/sudoku-solver/ - -Complexity: - Time: O(9^(empty cells)) worst case - Space: O(N^2) -""" - -from __future__ import annotations - - -class Sudoku: - """A Sudoku board solver.""" - - def __init__( - self, - board: list[list[str]], - row: int, - col: int, - ) -> None: - """Initialise the solver with the given board. - - Args: - board: 2D list of digits or '.' for empty cells. - row: Number of rows. - col: Number of columns. - """ - self.board = board - self.row = row - self.col = col - self.val = self._possible_values() - - def _possible_values(self) -> dict[tuple[int, int], list[str]]: - """Compute possible values for each empty cell. - - Returns: - Mapping from (row, col) to list of candidate digits. - """ - a = "123456789" - d: dict[tuple[str, int] | tuple[int, int], list[str]] = {} - val: dict[tuple[int, int], list[str]] = {} - for i in range(self.row): - for j in range(self.col): - ele = self.board[i][j] - if ele != ".": - d[("r", i)] = d.get(("r", i), []) + [ele] - d[("c", j)] = d.get(("c", j), []) + [ele] - d[(i // 3, j // 3)] = d.get((i // 3, j // 3), []) + [ele] - else: - val[(i, j)] = [] - for i, j in val: - inval = ( - d.get(("r", i), []) - + d.get(("c", j), []) - + d.get((i // 3, j // 3), []) - ) - val[(i, j)] = [n for n in a if n not in inval] - return val - - def solve(self) -> bool: - """Attempt to solve the board in place. - - Returns: - True if a solution was found. - """ - if len(self.val) == 0: - return True - kee = min(self.val.keys(), key=lambda x: len(self.val[x])) - nums = self.val[kee] - for n in nums: - update: dict[tuple[int, int], str | list[str]] = {kee: self.val[kee]} - if self._valid_one(n, kee, update) and self.solve(): - return True - self._undo(kee, update) - return False - - def _valid_one( - self, - n: str, - kee: tuple[int, int], - update: dict[tuple[int, int], str | list[str]], - ) -> bool: - """Place digit *n* at *kee* and propagate constraints. - - Args: - n: Digit to place. - kee: (row, col) coordinate. - update: Undo log (modified in place). - - Returns: - True if placement is valid. - """ - self.board[kee[0]][kee[1]] = n - del self.val[kee] - i, j = kee - for ind in list(self.val.keys()): - if n in self.val[ind] and ( - ind[0] == i - or ind[1] == j - or (ind[0] // 3, ind[1] // 3) == (i // 3, j // 3) - ): - update[ind] = n - self.val[ind].remove(n) - if len(self.val[ind]) == 0: - return False - return True - - def _undo( - self, - kee: tuple[int, int], - update: dict[tuple[int, int], str | list[str]], - ) -> None: - """Revert the placement at *kee* using *update*. - - Args: - kee: (row, col) coordinate. - update: Undo log. - """ - self.board[kee[0]][kee[1]] = "." - for k in update: - if k not in self.val: - self.val[k] = update[k] - else: - self.val[k].append(update[k]) - - def __str__(self) -> str: - """Return a string representation of the board. - - Returns: - Formatted board string. - """ - resp = "" - for i in range(self.row): - for j in range(self.col): - resp += f" {self.board[i][j]} " - resp += "\n" - return resp +from algorithms.matrix.sudoku_solver import Sudoku # noqa: F401 diff --git a/algorithms/graph/tarjan.py b/algorithms/graph/tarjan.py index 82b0a3e6d..9ab9352a7 100644 --- a/algorithms/graph/tarjan.py +++ b/algorithms/graph/tarjan.py @@ -1,5 +1,4 @@ -""" -Tarjan's Strongly Connected Components Algorithm +"""Tarjan's Strongly Connected Components Algorithm. Finds all strongly connected components in a directed graph. @@ -23,6 +22,7 @@ def __init__(self, dict_graph: dict[str, list[str]]) -> None: Args: dict_graph: Adjacency dict ``{vertex: [neighbours]}``. + """ self.graph = DirectedGraph(dict_graph) self.index = 0 @@ -42,6 +42,7 @@ def _strongconnect(self, vertex: object, sccs: list[list]) -> None: Args: vertex: Current vertex node. sccs: Accumulated list of SCCs. + """ vertex.index = self.index vertex.lowlink = self.index diff --git a/algorithms/graph/topological_sort_bfs.py b/algorithms/graph/topological_sort_bfs.py index 28e439df7..a21c0729b 100644 --- a/algorithms/graph/topological_sort_bfs.py +++ b/algorithms/graph/topological_sort_bfs.py @@ -1,5 +1,4 @@ -""" -Topological Sort (Kahn's Algorithm / BFS) +"""Topological Sort (Kahn's Algorithm / BFS). Computes a topological ordering of a directed acyclic graph. Raises ValueError when a cycle is detected. @@ -32,6 +31,7 @@ def topological_sort(vertices: int, edges: list[tuple[int, int]]) -> list[int]: Examples: >>> topological_sort(3, [(0, 1), (1, 2)]) [0, 1, 2] + """ graph: dict[int, list[int]] = defaultdict(list) @@ -60,6 +60,7 @@ def topological_sort(vertices: int, edges: list[tuple[int, int]]) -> list[int]: queue.append(neighbor) if processed != vertices: - raise ValueError("Cycle detected, topological sort failed") + msg = "Cycle detected, topological sort failed" + raise ValueError(msg) return sorted_order diff --git a/algorithms/graph/topological_sort_dfs.py b/algorithms/graph/topological_sort_dfs.py index 15ed8dcfb..c72d249d6 100644 --- a/algorithms/graph/topological_sort_dfs.py +++ b/algorithms/graph/topological_sort_dfs.py @@ -1,5 +1,4 @@ -""" -Topological Sort +"""Topological Sort. Topological sort produces a linear ordering of vertices in a directed acyclic graph (DAG) such that for every directed edge (u, v), vertex u @@ -33,6 +32,7 @@ def top_sort_recursive(graph: dict[str, list[str]]) -> list[str]: Examples: >>> top_sort_recursive({'a': ['b'], 'b': []}) ['b', 'a'] + """ order: list[str] = [] enter = set(graph) @@ -43,7 +43,8 @@ def _dfs(node: str) -> None: for neighbour in graph.get(node, ()): neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: - raise ValueError("cycle") + msg = "cycle" + raise ValueError(msg) if neighbour_state == _BLACK: continue enter.discard(neighbour) @@ -71,6 +72,7 @@ def top_sort(graph: dict[str, list[str]]) -> list[str]: Examples: >>> top_sort({'a': ['b'], 'b': []}) ['b', 'a'] + """ order: list[str] = [] enter = set(graph) @@ -83,7 +85,8 @@ def _is_ready(node: str) -> bool: for neighbour in neighbours: neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: - raise ValueError("cycle") + msg = "cycle" + raise ValueError(msg) if neighbour_state != _BLACK: return False return True @@ -97,7 +100,8 @@ def _is_ready(node: str) -> bool: for neighbour in graph.get(node, ()): neighbour_state = state.get(neighbour) if neighbour_state == _GRAY: - raise ValueError("cycle") + msg = "cycle" + raise ValueError(msg) if neighbour_state == _BLACK: continue enter.discard(neighbour) diff --git a/algorithms/graph/transitive_closure_dfs.py b/algorithms/graph/transitive_closure_dfs.py index 1c033de24..63cc6a7e2 100644 --- a/algorithms/graph/transitive_closure_dfs.py +++ b/algorithms/graph/transitive_closure_dfs.py @@ -1,5 +1,4 @@ -""" -Transitive Closure via DFS +"""Transitive Closure via DFS. Computes the transitive closure of a directed graph using depth-first search. @@ -22,6 +21,7 @@ def __init__(self, vertices: int) -> None: Args: vertices: Number of vertices. + """ self.vertex_count = vertices self.graph: dict[int, list[int]] = {} @@ -33,6 +33,7 @@ def add_edge(self, source: int, target: int) -> None: Args: source: Source vertex. target: Target vertex. + """ if source in self.graph: self.graph[source].append(target) @@ -45,6 +46,7 @@ def _dfs_util(self, source: int, target: int) -> None: Args: source: Origin vertex. target: Current vertex being explored. + """ self.closure[source][target] = 1 @@ -61,6 +63,7 @@ def transitive_closure(self) -> list[list[int]]: Examples: >>> g = Graph(2); g.add_edge(0, 1); g.transitive_closure() [[1, 1], [0, 1]] + """ for i in range(self.vertex_count): self._dfs_util(i, i) diff --git a/algorithms/graph/traversal.py b/algorithms/graph/traversal.py index 0441d444e..04a1febd7 100644 --- a/algorithms/graph/traversal.py +++ b/algorithms/graph/traversal.py @@ -1,5 +1,4 @@ -""" -Graph Traversal Algorithms +"""Graph Traversal Algorithms. Provides DFS and BFS traversal of a graph represented as an adjacency dictionary. @@ -28,6 +27,7 @@ def dfs_traverse(graph: dict[Any, list[Any]], start: Any) -> set[Any]: Examples: >>> sorted(dfs_traverse({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] + """ visited: set[Any] = set() stack = [start] @@ -54,6 +54,7 @@ def bfs_traverse(graph: dict[Any, list[Any]], start: Any) -> set[Any]: Examples: >>> sorted(bfs_traverse({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] + """ visited: set[Any] = set() queue = deque([start]) @@ -85,6 +86,7 @@ def dfs_traverse_recursive( Examples: >>> sorted(dfs_traverse_recursive({'a': ['b'], 'b': []}, 'a')) ['a', 'b'] + """ if visited is None: visited = set() diff --git a/algorithms/graph/walls_and_gates.py b/algorithms/graph/walls_and_gates.py index 738e87cdb..abd07cf6e 100644 --- a/algorithms/graph/walls_and_gates.py +++ b/algorithms/graph/walls_and_gates.py @@ -1,5 +1,4 @@ -""" -Walls and Gates +"""Walls and Gates. Fill each empty room (INF) with the distance to its nearest gate (0). Walls are represented by -1. @@ -23,6 +22,7 @@ def walls_and_gates(rooms: list[list[int]]) -> None: Examples: >>> r = [[float('inf'), 0]]; walls_and_gates(r); r [[1, 0]] + """ for i in range(len(rooms)): for j in range(len(rooms[0])): @@ -38,6 +38,7 @@ def _dfs(rooms: list[list[int]], i: int, j: int, depth: int) -> None: i: Row index. j: Column index. depth: Current distance from the gate. + """ if i < 0 or i >= len(rooms) or j < 0 or j >= len(rooms[0]): return diff --git a/algorithms/graph/word_ladder.py b/algorithms/graph/word_ladder.py index 99617d1af..f5f1b284d 100644 --- a/algorithms/graph/word_ladder.py +++ b/algorithms/graph/word_ladder.py @@ -1,5 +1,4 @@ -""" -Word Ladder (Bidirectional BFS) +"""Word Ladder (Bidirectional BFS). Given two words and a dictionary, find the length of the shortest transformation sequence where only one letter changes at each step and @@ -14,7 +13,10 @@ from __future__ import annotations -from collections.abc import Iterator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator def ladder_length(begin_word: str, end_word: str, word_list: list[str]) -> int: @@ -31,6 +33,7 @@ def ladder_length(begin_word: str, end_word: str, word_list: list[str]) -> int: Examples: >>> ladder_length('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log']) 5 + """ if len(begin_word) != len(end_word): return -1 @@ -71,6 +74,7 @@ def _word_range(word: str) -> Iterator[str]: Yields: Words with a single character changed. + """ for ind in range(len(word)): temp = word[ind] diff --git a/algorithms/greedy/gale_shapley.py b/algorithms/greedy/gale_shapley.py index 7bf8a686b..6555b0368 100644 --- a/algorithms/greedy/gale_shapley.py +++ b/algorithms/greedy/gale_shapley.py @@ -1,5 +1,4 @@ -""" -Gale-Shapley Stable Matching +"""Gale-Shapley Stable Matching. Solves the stable matching (stable marriage) problem. Given N men and N women with ranked preferences, produces a stable matching where no pair would prefer @@ -35,10 +34,11 @@ def gale_shapley( >>> women = {"W1": ["M2", "M1"], "W2": ["M1", "M2"]} >>> sorted(gale_shapley(men, women).items()) [('M1', 'W2'), ('M2', 'W1')] + """ men_available: list[str] = list(men.keys()) married: dict[str, str] = {} - proposal_counts: dict[str, int] = {man: 0 for man in men} + proposal_counts: dict[str, int] = dict.fromkeys(men, 0) while men_available: man = men_available.pop(0) diff --git a/algorithms/greedy/max_contiguous_subsequence_sum.py b/algorithms/greedy/max_contiguous_subsequence_sum.py index eaf71dde8..bbe0d774c 100644 --- a/algorithms/greedy/max_contiguous_subsequence_sum.py +++ b/algorithms/greedy/max_contiguous_subsequence_sum.py @@ -1,5 +1,4 @@ -""" -Maximum Contiguous Subsequence Sum (Kadane's Algorithm) +"""Maximum Contiguous Subsequence Sum (Kadane's Algorithm). Finds the maximum sum of a contiguous sub-array within a one-dimensional array of numbers. The algorithm is greedy / dynamic-programming hybrid. @@ -33,6 +32,7 @@ def max_contiguous_subsequence_sum(arr: list[int]) -> int: -1 >>> max_contiguous_subsequence_sum([]) 0 + """ if not arr: return 0 diff --git a/algorithms/heap/k_closest_points.py b/algorithms/heap/k_closest_points.py index 0b47a2e40..52b20dd87 100644 --- a/algorithms/heap/k_closest_points.py +++ b/algorithms/heap/k_closest_points.py @@ -1,5 +1,4 @@ -""" -K Closest Points to Origin +"""K Closest Points to Origin. Given a list of points, find the k closest to the origin using a max heap of size k. For each subsequent point, replace the heap root if @@ -35,6 +34,7 @@ def k_closest( Examples: >>> k_closest([(1, 0), (-1, 0), (2, 3)], 2) [(-1, 0), (1, 0)] + """ heap = [(-_distance(p, origin), p) for p in points[:k]] heapify(heap) @@ -55,5 +55,6 @@ def _distance(point: tuple[int, int], origin: tuple[int, int] = (0, 0)) -> int: Returns: Squared Euclidean distance. + """ return (point[0] - origin[0]) ** 2 + (point[1] - origin[1]) ** 2 diff --git a/algorithms/heap/merge_sorted_k_lists.py b/algorithms/heap/merge_sorted_k_lists.py index 865e5ef0d..37e68e07a 100644 --- a/algorithms/heap/merge_sorted_k_lists.py +++ b/algorithms/heap/merge_sorted_k_lists.py @@ -1,5 +1,4 @@ -""" -Merge K Sorted Linked Lists +"""Merge K Sorted Linked Lists. Merge k sorted linked lists into one sorted linked list using a heap for efficient minimum extraction. @@ -21,6 +20,7 @@ class ListNode: Args: val: The node value. + """ def __init__(self, val: int) -> None: @@ -43,6 +43,7 @@ def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None: >>> result = merge_k_lists([n1, n2]) >>> result.val 1 + """ dummy = node = ListNode(0) heap: list[tuple[int, int, ListNode]] = [] @@ -51,7 +52,7 @@ def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None: heap.append((head.val, idx, head)) heapify(heap) while heap: - val, idx, n_val = heap[0] + _val, idx, n_val = heap[0] if n_val.next is None: heappop(heap) else: diff --git a/algorithms/heap/skyline.py b/algorithms/heap/skyline.py index cec00da57..73dc50f01 100644 --- a/algorithms/heap/skyline.py +++ b/algorithms/heap/skyline.py @@ -1,5 +1,4 @@ -""" -Skyline Problem +"""Skyline Problem. Given building triplets [left, right, height], compute the skyline contour as a list of key points using a heap-based sweep line approach. @@ -29,12 +28,13 @@ def get_skyline(lrh: list[list[int]]) -> list[list[int]]: Examples: >>> get_skyline([[2, 9, 10], [3, 7, 15]]) [[2, 10], [3, 15], [7, 10], [9, 0]] + """ skyline: list[list[int]] = [] live: list[list[int]] = [] i, n = 0, len(lrh) while i < n or live: - if not live or i < n and lrh[i][0] <= -live[0][1]: + if not live or (i < n and lrh[i][0] <= -live[0][1]): x = lrh[i][0] while i < n and lrh[i][0] == x: heapq.heappush(live, [-lrh[i][2], -lrh[i][1]]) diff --git a/algorithms/heap/sliding_window_max.py b/algorithms/heap/sliding_window_max.py index 5cbf6d7f1..bccc94813 100644 --- a/algorithms/heap/sliding_window_max.py +++ b/algorithms/heap/sliding_window_max.py @@ -1,5 +1,4 @@ -""" -Sliding Window Maximum (Heap-based) +"""Sliding Window Maximum (Heap-based). Given an array and a window size k, find the maximum element in each sliding window using a deque that maintains decreasing order of values. @@ -29,6 +28,7 @@ def max_sliding_window(nums: list[int], k: int) -> list[int]: Examples: >>> max_sliding_window([1, 3, -1, -3, 5, 3, 6, 7], 3) [3, 3, 5, 5, 6, 7] + """ if not nums: return nums diff --git a/algorithms/linked_list/__init__.py b/algorithms/linked_list/__init__.py index dca587d97..a4a19f090 100644 --- a/algorithms/linked_list/__init__.py +++ b/algorithms/linked_list/__init__.py @@ -47,10 +47,12 @@ from algorithms.linked_list.swap_in_pairs import swap_pairs __all__ = [ + "DoublyLinkedListNode", + "RandomListNode", + "SinglyLinkedListNode", "add_two_numbers", "convert_to_list", "convert_to_str", - "RandomListNode", "copy_random_pointer_v1", "copy_random_pointer_v2", "delete_node", @@ -64,8 +66,6 @@ "kth_to_last", "kth_to_last_dict", "kth_to_last_eval", - "DoublyLinkedListNode", - "SinglyLinkedListNode", "merge_two_list", "merge_two_list_recur", "partition", diff --git a/algorithms/linked_list/add_two_numbers.py b/algorithms/linked_list/add_two_numbers.py index ccf1611cb..b5926ba04 100644 --- a/algorithms/linked_list/add_two_numbers.py +++ b/algorithms/linked_list/add_two_numbers.py @@ -1,5 +1,4 @@ -""" -Add Two Numbers (Linked List) +"""Add Two Numbers (Linked List). Given two non-empty linked lists representing two non-negative integers with digits stored in reverse order, add the two numbers and return the sum as a @@ -37,6 +36,7 @@ def add_two_numbers(left: Node, right: Node) -> Node: >>> l2 = Node(5); l2.next = Node(6); l2.next.next = Node(4) >>> convert_to_str(add_two_numbers(l1, l2)) '708' + """ head = Node(0) current = head @@ -68,6 +68,7 @@ def convert_to_list(number: int) -> Node | None: Examples: >>> convert_to_str(convert_to_list(112)) '211' + """ if number < 0: return None @@ -98,6 +99,7 @@ def convert_to_str(node: Node | None) -> str: >>> n = Node(2); n.next = Node(4); n.next.next = Node(3) >>> convert_to_str(n) '243' + """ result = "" while node: diff --git a/algorithms/linked_list/copy_random_pointer.py b/algorithms/linked_list/copy_random_pointer.py index a2054eba8..ed0ac20d1 100644 --- a/algorithms/linked_list/copy_random_pointer.py +++ b/algorithms/linked_list/copy_random_pointer.py @@ -1,5 +1,4 @@ -""" -Copy List with Random Pointer +"""Copy List with Random Pointer. Given a linked list where each node contains an additional random pointer that could point to any node in the list or null, return a deep copy of the list. @@ -40,6 +39,7 @@ def copy_random_pointer_v1(head: RandomListNode | None) -> RandomListNode | None >>> copied = copy_random_pointer_v1(node) >>> copied.label == 1 and copied.random is copied True + """ node_map: dict[RandomListNode, RandomListNode] = {} current = head @@ -69,9 +69,10 @@ def copy_random_pointer_v2(head: RandomListNode | None) -> RandomListNode | None >>> copied = copy_random_pointer_v2(node) >>> copied.label == 1 and copied.random is copied True + """ copy: defaultdict[RandomListNode | None, RandomListNode | None] = defaultdict( - lambda: RandomListNode(0) + lambda: RandomListNode(0), ) copy[None] = None node = head diff --git a/algorithms/linked_list/delete_node.py b/algorithms/linked_list/delete_node.py index 89411c7cf..e001b4833 100644 --- a/algorithms/linked_list/delete_node.py +++ b/algorithms/linked_list/delete_node.py @@ -1,5 +1,4 @@ -""" -Delete Node in a Linked List +"""Delete Node in a Linked List. Given only access to a node (not the tail) in a singly linked list, delete that node by copying the next node's value and skipping over it. @@ -37,6 +36,7 @@ def delete_node(node: Node | None) -> None: >>> delete_node(head.next) >>> head.next.val 3 + """ if node is None or node.next is None: raise ValueError diff --git a/algorithms/linked_list/first_cyclic_node.py b/algorithms/linked_list/first_cyclic_node.py index 8c6dd5d87..0bdbb183c 100644 --- a/algorithms/linked_list/first_cyclic_node.py +++ b/algorithms/linked_list/first_cyclic_node.py @@ -1,5 +1,4 @@ -""" -First Cyclic Node +"""First Cyclic Node. Given a linked list, find the first node of a cycle in it using Floyd's cycle-finding algorithm (Tortoise and Hare). @@ -34,6 +33,7 @@ def first_cyclic_node(head: Node | None) -> Node | None: >>> a.next = b; b.next = c; c.next = b >>> first_cyclic_node(a).val 2 + """ runner = walker = head while runner and runner.next: diff --git a/algorithms/linked_list/intersection.py b/algorithms/linked_list/intersection.py index c5b817a63..bf7761361 100644 --- a/algorithms/linked_list/intersection.py +++ b/algorithms/linked_list/intersection.py @@ -1,5 +1,4 @@ -""" -Intersection of Two Linked Lists +"""Intersection of Two Linked Lists. Given two singly linked lists that converge at some node, find and return the intersecting node. The node identity (not value) is the unique identifier. @@ -36,6 +35,7 @@ def intersection(h1: Node, h2: Node) -> Node | None: >>> b = Node(2); b.next = shared >>> intersection(a, b).val 7 + """ count = 0 flag = None @@ -70,8 +70,7 @@ def intersection(h1: Node, h2: Node) -> Node | None: if longer == shorter: return longer - else: - longer = longer.next - shorter = shorter.next + longer = longer.next + shorter = shorter.next return None diff --git a/algorithms/linked_list/is_cyclic.py b/algorithms/linked_list/is_cyclic.py index 8db794efa..3d81c5bdf 100644 --- a/algorithms/linked_list/is_cyclic.py +++ b/algorithms/linked_list/is_cyclic.py @@ -1,5 +1,4 @@ -""" -Linked List Cycle Detection +"""Linked List Cycle Detection. Given a linked list, determine if it has a cycle using Floyd's Tortoise and Hare algorithm without extra space. @@ -36,6 +35,7 @@ def is_cyclic(head: Node | None) -> bool: >>> c = Node(3); c.next = Node(4) >>> is_cyclic(c) False + """ if not head: return False diff --git a/algorithms/linked_list/is_palindrome.py b/algorithms/linked_list/is_palindrome.py index 371cdc346..1be64f01e 100644 --- a/algorithms/linked_list/is_palindrome.py +++ b/algorithms/linked_list/is_palindrome.py @@ -1,5 +1,4 @@ -""" -Palindrome Linked List +"""Palindrome Linked List. Determine whether a singly linked list is a palindrome. Three approaches are provided: reverse-half, stack-based, and dictionary-based. @@ -26,6 +25,7 @@ def is_palindrome(head: object | None) -> bool: Examples: >>> is_palindrome(None) True + """ if not head: return True @@ -61,6 +61,7 @@ def is_palindrome_stack(head: object | None) -> bool: Examples: >>> is_palindrome_stack(None) True + """ if not head or not head.next: return True @@ -97,6 +98,7 @@ def is_palindrome_dict(head: object | None) -> bool: Examples: >>> is_palindrome_dict(None) True + """ if not head or not head.next: return True diff --git a/algorithms/linked_list/is_sorted.py b/algorithms/linked_list/is_sorted.py index 0cdf3416c..47dfba590 100644 --- a/algorithms/linked_list/is_sorted.py +++ b/algorithms/linked_list/is_sorted.py @@ -1,5 +1,4 @@ -""" -Is Sorted Linked List +"""Is Sorted Linked List. Given a linked list, determine whether the list is sorted in non-decreasing order. An empty list is considered sorted. @@ -26,6 +25,7 @@ def is_sorted(head: object | None) -> bool: Examples: >>> is_sorted(None) True + """ if not head: return True diff --git a/algorithms/linked_list/kth_to_last.py b/algorithms/linked_list/kth_to_last.py index 962d0451c..987a6795a 100644 --- a/algorithms/linked_list/kth_to_last.py +++ b/algorithms/linked_list/kth_to_last.py @@ -1,5 +1,4 @@ -""" -Kth to Last Element +"""Kth to Last Element. Find the kth to last element of a singly linked list. Three approaches are provided: eval-based, dictionary-based, and two-pointer iterative. @@ -34,6 +33,7 @@ def kth_to_last_eval(head: Node, k: int) -> Node | bool: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last_eval(a, 1).val 2 + """ if not isinstance(k, int) or not head.val: return False @@ -65,6 +65,7 @@ def kth_to_last_dict(head: Node | None, k: int) -> Node | bool: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last_dict(a, 1).val 2 + """ if not (head and k > -1): return False @@ -97,6 +98,7 @@ def kth_to_last(head: Node | None, k: int) -> Node | bool: >>> a = Node(1); b = Node(2); a.next = b >>> kth_to_last(a, 1).val 2 + """ if not (head or k > -1): return False diff --git a/algorithms/linked_list/merge_two_list.py b/algorithms/linked_list/merge_two_list.py index cbab901eb..a17ec8a52 100644 --- a/algorithms/linked_list/merge_two_list.py +++ b/algorithms/linked_list/merge_two_list.py @@ -1,5 +1,4 @@ -""" -Merge Two Sorted Lists +"""Merge Two Sorted Lists. Merge two sorted linked lists into a single sorted list by splicing together the nodes of the two input lists. @@ -36,6 +35,7 @@ def merge_two_list(l1: Node | None, l2: Node | None) -> Node | None: >>> result = merge_two_list(a, b) >>> result.val 1 + """ sentinel = current = Node(0) while l1 and l2: @@ -66,12 +66,12 @@ def merge_two_list_recur(l1: Node | None, l2: Node | None) -> Node | None: >>> result = merge_two_list_recur(a, b) >>> result.val 1 + """ if not l1 or not l2: return l1 or l2 if l1.val < l2.val: l1.next = merge_two_list_recur(l1.next, l2) return l1 - else: - l2.next = merge_two_list_recur(l1, l2.next) - return l2 + l2.next = merge_two_list_recur(l1, l2.next) + return l2 diff --git a/algorithms/linked_list/partition.py b/algorithms/linked_list/partition.py index 405a74ab3..52ffd59f3 100644 --- a/algorithms/linked_list/partition.py +++ b/algorithms/linked_list/partition.py @@ -1,5 +1,4 @@ -""" -Partition Linked List +"""Partition Linked List. Partition a linked list around a value x so that all nodes with values less than x come before nodes with values greater than or equal to x. @@ -37,6 +36,7 @@ def partition(head: Node | None, x: int) -> None: >>> a = Node(3); b = Node(5); c = Node(1) >>> a.next = b; b.next = c >>> partition(a, 5) + """ left = None right = None @@ -46,14 +46,13 @@ def partition(head: Node | None, x: int) -> None: if int(current.val) >= x: if not right: right = current + elif not left: + left = current else: - if not left: - left = current - else: - prev.next = current.next - left.next = current - left = current - left.next = right + prev.next = current.next + left.next = current + left = current + left.next = right if prev and prev.next is None: break prev = current diff --git a/algorithms/linked_list/remove_duplicates.py b/algorithms/linked_list/remove_duplicates.py index a7d65340f..5b5c20302 100644 --- a/algorithms/linked_list/remove_duplicates.py +++ b/algorithms/linked_list/remove_duplicates.py @@ -1,5 +1,4 @@ -""" -Remove Duplicates from Linked List +"""Remove Duplicates from Linked List. Remove duplicate values from an unsorted linked list. Two approaches are provided: hash-set-based (O(n) time, O(n) space) and runner technique @@ -36,6 +35,7 @@ def remove_dups(head: Node | None) -> None: >>> remove_dups(a) >>> a.next.val 2 + """ seen: set[object] = set() prev = Node() @@ -65,6 +65,7 @@ def remove_dups_wothout_set(head: Node | None) -> None: >>> remove_dups_wothout_set(a) >>> a.next.val 2 + """ current = head while current: diff --git a/algorithms/linked_list/remove_range.py b/algorithms/linked_list/remove_range.py index 302005a7a..601317b82 100644 --- a/algorithms/linked_list/remove_range.py +++ b/algorithms/linked_list/remove_range.py @@ -1,5 +1,4 @@ -""" -Remove Range from Linked List +"""Remove Range from Linked List. Given a linked list and a start and end index, remove the elements at those indexes (inclusive) from the list. @@ -28,6 +27,7 @@ def remove_range(head: object | None, start: int, end: int) -> object | None: Examples: >>> remove_range(None, 0, 0) is None True + """ assert start <= end if start == 0: diff --git a/algorithms/linked_list/reverse.py b/algorithms/linked_list/reverse.py index a84b51580..7c25dc155 100644 --- a/algorithms/linked_list/reverse.py +++ b/algorithms/linked_list/reverse.py @@ -1,5 +1,4 @@ -""" -Reverse Linked List +"""Reverse Linked List. Reverse a singly linked list. Both iterative and recursive solutions are provided. @@ -26,6 +25,7 @@ def reverse_list(head: object | None) -> object | None: Examples: >>> reverse_list(None) is None True + """ if not head or not head.next: return head @@ -50,6 +50,7 @@ def reverse_list_recursive(head: object | None) -> object | None: Examples: >>> reverse_list_recursive(None) is None True + """ if head is None or head.next is None: return head diff --git a/algorithms/linked_list/rotate_list.py b/algorithms/linked_list/rotate_list.py index 4d3be23ad..1754360a1 100644 --- a/algorithms/linked_list/rotate_list.py +++ b/algorithms/linked_list/rotate_list.py @@ -1,5 +1,4 @@ -""" -Rotate List +"""Rotate List. Given a linked list, rotate the list to the right by k places, where k is non-negative. @@ -27,6 +26,7 @@ def rotate_right(head: object | None, k: int) -> object | None: Examples: >>> rotate_right(None, 5) is None True + """ if not head or not head.next: return head diff --git a/algorithms/linked_list/swap_in_pairs.py b/algorithms/linked_list/swap_in_pairs.py index 14353c37e..1bdc3b64c 100644 --- a/algorithms/linked_list/swap_in_pairs.py +++ b/algorithms/linked_list/swap_in_pairs.py @@ -1,5 +1,4 @@ -""" -Swap Nodes in Pairs +"""Swap Nodes in Pairs. Given a linked list, swap every two adjacent nodes and return the new head. Only node links are changed, not node values. @@ -34,6 +33,7 @@ def swap_pairs(head: Node | None) -> Node | None: >>> result = swap_pairs(a) >>> result.val 2 + """ if not head: return head diff --git a/algorithms/map/__init__.py b/algorithms/map/__init__.py index 7e8698e1f..5a22842ab 100644 --- a/algorithms/map/__init__.py +++ b/algorithms/map/__init__.py @@ -17,13 +17,13 @@ __all__ = [ "HashTable", + "RandomizedSet", "ResizableHashTable", + "SeparateChainingHashTable", "is_anagram", "is_isomorphic", "is_valid_sudoku", "longest_palindromic_subsequence", "max_common_sub_string", - "RandomizedSet", - "SeparateChainingHashTable", "word_pattern", ] diff --git a/algorithms/map/is_anagram.py b/algorithms/map/is_anagram.py index 503640369..7a460d8c8 100644 --- a/algorithms/map/is_anagram.py +++ b/algorithms/map/is_anagram.py @@ -1,5 +1,4 @@ -""" -Is Anagram +"""Is Anagram. Determine whether two strings are anagrams of each other by comparing character frequency maps. @@ -29,6 +28,7 @@ def is_anagram(s: str, t: str) -> bool: True >>> is_anagram("rat", "car") False + """ freq_s: dict[str, int] = {} freq_t: dict[str, int] = {} diff --git a/algorithms/map/is_isomorphic.py b/algorithms/map/is_isomorphic.py index 0bdd86930..e8a178f34 100644 --- a/algorithms/map/is_isomorphic.py +++ b/algorithms/map/is_isomorphic.py @@ -1,5 +1,4 @@ -""" -Isomorphic Strings +"""Isomorphic Strings. Determine if two strings are isomorphic. Two strings are isomorphic if characters in s can be mapped to characters in t while preserving order, @@ -30,6 +29,7 @@ def is_isomorphic(s: str, t: str) -> bool: True >>> is_isomorphic("foo", "bar") False + """ if len(s) != len(t): return False @@ -41,7 +41,6 @@ def is_isomorphic(s: str, t: str) -> bool: return False mapping[s[i]] = t[i] mapped_values.add(t[i]) - else: - if mapping[s[i]] != t[i]: - return False + elif mapping[s[i]] != t[i]: + return False return True diff --git a/algorithms/map/longest_common_substring.py b/algorithms/map/longest_common_substring.py index d7cc8b366..53ae0bd84 100644 --- a/algorithms/map/longest_common_substring.py +++ b/algorithms/map/longest_common_substring.py @@ -1,5 +1,4 @@ -""" -Longest Common Substring +"""Longest Common Substring. Given two strings where the second contains all distinct characters, find the longest common substring using index mapping. @@ -30,6 +29,7 @@ def max_common_sub_string(s1: str, s2: str) -> str: Examples: >>> max_common_sub_string("abcdef", "acdbef") 'cd' + """ char_index = {s2[i]: i for i in range(len(s2))} max_length = 0 diff --git a/algorithms/map/longest_palindromic_subsequence.py b/algorithms/map/longest_palindromic_subsequence.py index 8f1a0e40f..31a97f87d 100644 --- a/algorithms/map/longest_palindromic_subsequence.py +++ b/algorithms/map/longest_palindromic_subsequence.py @@ -1,5 +1,4 @@ -""" -Longest Palindromic Substring +"""Longest Palindromic Substring. Find the length of the longest palindromic substring using dynamic programming with two rolling arrays. @@ -28,6 +27,7 @@ def longest_palindromic_subsequence(s: str) -> int: 3 >>> longest_palindromic_subsequence("cbbd") 2 + """ length = len(s) previous_row = [0] * length @@ -40,14 +40,11 @@ def longest_palindromic_subsequence(s: str) -> int: if s[start] == s[end]: current_row[start] = 1 span = end - start + 1 - if longest_length < span: - longest_length = span - else: - if s[start] == s[end] and previous_row[start + 1]: - current_row[start] = 1 - span = end - start + 1 - if longest_length < span: - longest_length = span + longest_length = max(longest_length, span) + elif s[start] == s[end] and previous_row[start + 1]: + current_row[start] = 1 + span = end - start + 1 + longest_length = max(longest_length, span) previous_row = current_row current_row = [0] * length diff --git a/algorithms/map/randomized_set.py b/algorithms/map/randomized_set.py index 7c123b7a9..be1aa6474 100644 --- a/algorithms/map/randomized_set.py +++ b/algorithms/map/randomized_set.py @@ -1,5 +1,4 @@ -""" -Randomized Set +"""Randomized Set. Design a data structure that supports insert, remove, and getRandom in average O(1) time. Uses a list for random access and a dictionary @@ -28,6 +27,7 @@ class RandomizedSet: False >>> rs.remove(1) True + """ def __init__(self) -> None: @@ -43,6 +43,7 @@ def insert(self, val: int) -> bool: Returns: True if the value was inserted, False if already present. + """ if val not in self.idxs: self.nums.append(val) @@ -58,6 +59,7 @@ def remove(self, val: int) -> bool: Returns: True if the value was removed, False if not present. + """ if val in self.idxs: idx, last = self.idxs[val], self.nums[-1] @@ -72,6 +74,7 @@ def get_random(self) -> int: Returns: A randomly chosen element. + """ idx = random.randint(0, len(self.nums) - 1) return self.nums[idx] diff --git a/algorithms/map/valid_sudoku.py b/algorithms/map/valid_sudoku.py index 4cf8ea1e2..d77990df1 100644 --- a/algorithms/map/valid_sudoku.py +++ b/algorithms/map/valid_sudoku.py @@ -1,5 +1,4 @@ -""" -Valid Sudoku +"""Valid Sudoku. Determine if a partially filled 9x9 Sudoku board is valid. A board is valid if each row, column, and 3x3 sub-box contains no duplicate digits. @@ -26,6 +25,7 @@ def is_valid_sudoku(board: list[list[str]]) -> bool: Examples: >>> is_valid_sudoku([['.' for _ in range(9)] for _ in range(9)]) True + """ seen: list[tuple[str, ...]] = [] for i, row in enumerate(board): diff --git a/algorithms/map/word_pattern.py b/algorithms/map/word_pattern.py index c381382bb..124ab033d 100644 --- a/algorithms/map/word_pattern.py +++ b/algorithms/map/word_pattern.py @@ -1,5 +1,4 @@ -""" -Word Pattern +"""Word Pattern. Given a pattern and a string, determine if the string follows the same pattern via a bijection between pattern letters and words. @@ -29,6 +28,7 @@ def word_pattern(pattern: str, string: str) -> bool: True >>> word_pattern("abba", "dog cat cat fish") False + """ mapping: dict[str, str] = {} mapped_values: set[str] = set() @@ -41,7 +41,6 @@ def word_pattern(pattern: str, string: str) -> bool: return False mapping[pattern[i]] = words[i] mapped_values.add(words[i]) - else: - if mapping[pattern[i]] != words[i]: - return False + elif mapping[pattern[i]] != words[i]: + return False return True diff --git a/algorithms/math/__init__.py b/algorithms/math/__init__.py index d3d27e37a..45c07d43e 100644 --- a/algorithms/math/__init__.py +++ b/algorithms/math/__init__.py @@ -1,17 +1,20 @@ -""" -Collection of mathematical algorithms and functions. -""" +"""Collection of mathematical algorithms and functions.""" from __future__ import annotations # Module-level imports for backward compatibility (tests use module.function syntax) from algorithms.math import ( - chinese_remainder_theorem, # noqa: E402 - fft, # noqa: E402 - hailstone, # noqa: E402 - modular_inverse, # type: ignore[no-redef] # noqa: E402 + chinese_remainder_theorem, + fft, + hailstone, + modular_inverse, # type: ignore[no-redef] ) from algorithms.math.base_conversion import base_to_int, int_to_base +from algorithms.math.all_factors import ( + get_factors, + get_factors_iterative1, + get_factors_iterative2, +) from algorithms.math.chebyshev_distance import chebyshev_distance from algorithms.math.combination import combination, combination_memo from algorithms.math.cosine_similarity import cosine_similarity @@ -66,8 +69,13 @@ from algorithms.math.surface_area_of_torus import surface_area_of_torus __all__ = [ + "alice_private_key", + "alice_public_key", + "alice_shared_key", "base_to_int", - "int_to_base", + "bob_private_key", + "bob_public_key", + "bob_shared_key", "chebyshev_distance", "chinese_remainder_theorem", "combination", @@ -75,59 +83,57 @@ "cosine_similarity", "decimal_to_binary_ip", "decimal_to_binary_util", - "alice_private_key", - "alice_public_key", - "alice_shared_key", - "bob_private_key", - "bob_public_key", - "bob_shared_key", + "decrypt", "diffie_hellman_key_exchange", "distance_between_two_points", + "encrypt", "euler_totient", "extended_gcd", "factorial", "factorial_recur", "fft", + "find_next_square", + "find_next_square2", + "find_nth_digit", "find_order", "find_primitive_root", + "get_factors", + "get_factors_iterative1", + "get_factors_iterative2", "gcd", "gcd_bit", - "lcm", - "trailing_zero", "gen_strobogrammatic", - "strobogrammatic_in_range", + "generate_key", "geometric_mean", "geometric_mean_logarithm", + "get_primes", + "goldbach", "hailstone", + "int_to_base", + "is_prime", "is_strobogrammatic", "is_strobogrammatic2", "krishnamurthy_number", + "lcm", + "linear_regression", "magic_number", + "manhattan_distance", "modular_exponential", "modular_inverse", "next_bigger", - "find_next_square", - "find_next_square2", - "find_nth_digit", "num_digits", "num_perfect_squares", "power", "power_recur", "prime_check", - "get_primes", "pythagoras", - "is_prime", + "r_squared", "recursive_binomial_coefficient", - "decrypt", - "encrypt", - "generate_key", + "rmse", "square_root", + "strobogrammatic_in_range", "sum_dig_pow", "surface_area_of_torus", - "linear_regression", - "r_squared", - "rmse", - "manhattan_distance", - "goldbach", + "trailing_zero", "verify_goldbach", ] diff --git a/algorithms/math/all_factors.py b/algorithms/math/all_factors.py new file mode 100644 index 000000000..ec7df04ee --- /dev/null +++ b/algorithms/math/all_factors.py @@ -0,0 +1,102 @@ +"""Factor Combinations. + +Given an integer n, return all possible combinations of its factors +(excluding 1 and n itself in the factorisation). + +Reference: https://leetcode.com/problems/factor-combinations/ + +Complexity: + Time: O(n^(1/2) * log n) (approximate, depends on factor density) + Space: O(log n) +""" + +from __future__ import annotations + + +def get_factors(n: int) -> list[list[int]]: + """Return all factor combinations of *n* using recursion. + + Args: + n: The number to factorise. + + Returns: + List of factor lists. + + Examples: + >>> get_factors(12) + [[2, 6], [2, 2, 3], [3, 4]] + + """ + + def _factor( + n: int, + i: int, + combi: list[int], + res: list[list[int]], + ) -> list[list[int]]: + while i * i <= n: + if n % i == 0: + res += ([*combi, i, int(n / i)],) + _factor(n / i, i, [*combi, i], res) + i += 1 + return res + + return _factor(n, 2, [], []) + + +def get_factors_iterative1(n: int) -> list[list[int]]: + """Return all factor combinations using an explicit stack. + + Args: + n: The number to factorise. + + Returns: + List of factor lists. + + Examples: + >>> get_factors_iterative1(12) + [[2, 6], [3, 4], [2, 2, 3]] + + """ + todo: list[tuple[int, int, list[int]]] = [(n, 2, [])] + res: list[list[int]] = [] + while todo: + n, i, combi = todo.pop() + while i * i <= n: + if n % i == 0: + res += ([*combi, i, n // i],) + todo.append((n // i, i, [*combi, i])) + i += 1 + return res + + +def get_factors_iterative2(n: int) -> list[list[int]]: + """Return all factor combinations using a stack-based approach. + + Args: + n: The number to factorise. + + Returns: + List of factor lists. + + Examples: + >>> get_factors_iterative2(12) + [[2, 2, 3], [2, 6], [3, 4]] + + """ + ans: list[list[int]] = [] + stack: list[int] = [] + x = 2 + while True: + if x > n // x: + if not stack: + return ans + ans.append([*stack, n]) + x = stack.pop() + n *= x + x += 1 + elif n % x == 0: + stack.append(x) + n //= x + else: + x += 1 diff --git a/algorithms/math/base_conversion.py b/algorithms/math/base_conversion.py index f332dc6f6..ff3173ef0 100644 --- a/algorithms/math/base_conversion.py +++ b/algorithms/math/base_conversion.py @@ -1,5 +1,4 @@ -""" -Integer Base Conversion +"""Integer Base Conversion. Convert integers between arbitrary bases (2-36). Supports conversion from integer to string representation in a given base, and vice versa. @@ -33,6 +32,7 @@ def int_to_base(num: int, base: int) -> str: 'FF' >>> int_to_base(0, 2) '0' + """ is_negative = False if num == 0: @@ -65,6 +65,7 @@ def base_to_int(str_to_convert: str, base: int) -> int: 5 >>> base_to_int('FF', 16) 255 + """ digit = {} for ind, char in enumerate(string.digits + string.ascii_uppercase): diff --git a/algorithms/math/chinese_remainder_theorem.py b/algorithms/math/chinese_remainder_theorem.py index 64788a3a9..7d2bbbd9a 100644 --- a/algorithms/math/chinese_remainder_theorem.py +++ b/algorithms/math/chinese_remainder_theorem.py @@ -1,5 +1,4 @@ -""" -Chinese Remainder Theorem +"""Chinese Remainder Theorem. Solves a system of simultaneous congruences using the Chinese Remainder Theorem. Given pairwise coprime moduli, finds the smallest positive integer @@ -33,16 +32,21 @@ def solve_chinese_remainder(nums: list[int], rems: list[int]) -> int: Examples: >>> solve_chinese_remainder([3, 7, 10], [2, 3, 3]) 143 + """ - if not len(nums) == len(rems): - raise Exception("nums and rems should have equal length") + if len(nums) != len(rems): + msg = "nums and rems should have equal length" + raise Exception(msg) if not len(nums) > 0: - raise Exception("Lists nums and rems need to contain at least one element") + msg = "Lists nums and rems need to contain at least one element" + raise Exception(msg) for num in nums: if not num > 1: - raise Exception("All numbers in nums needs to be > 1") + msg = "All numbers in nums needs to be > 1" + raise Exception(msg) if not _check_coprime(nums): - raise Exception("All pairs of numbers in nums are not coprime") + msg = "All pairs of numbers in nums are not coprime" + raise Exception(msg) k = len(nums) x = 1 while True: @@ -64,6 +68,7 @@ def _check_coprime(list_to_check: list[int]) -> bool: Returns: True if all pairs are coprime, False otherwise. + """ for ind, num in enumerate(list_to_check): for num2 in list_to_check[ind + 1 :]: diff --git a/algorithms/math/combination.py b/algorithms/math/combination.py index cbcf8ab97..077081e03 100644 --- a/algorithms/math/combination.py +++ b/algorithms/math/combination.py @@ -1,5 +1,4 @@ -""" -Combinations (nCr) +"""Combinations (nCr). Calculate the number of ways to choose r items from n items (binomial coefficient) using recursive and memoized approaches. @@ -29,8 +28,9 @@ def combination(n: int, r: int) -> int: 10 >>> combination(10, 5) 252 + """ - if n == r or r == 0: + if r in (n, 0): return 1 return combination(n - 1, r - 1) + combination(n - 1, r) @@ -48,11 +48,12 @@ def combination_memo(n: int, r: int) -> int: Examples: >>> combination_memo(50, 10) 10272278170 + """ memo: dict[tuple[int, int], int] = {} def _recur(n: int, r: int) -> int: - if n == r or r == 0: + if r in (n, 0): return 1 if (n, r) not in memo: memo[(n, r)] = _recur(n - 1, r - 1) + _recur(n - 1, r) diff --git a/algorithms/math/cosine_similarity.py b/algorithms/math/cosine_similarity.py index 7b1219ad3..bace2ff14 100644 --- a/algorithms/math/cosine_similarity.py +++ b/algorithms/math/cosine_similarity.py @@ -1,5 +1,4 @@ -""" -Cosine Similarity +"""Cosine Similarity. Calculate the cosine similarity between two vectors, which measures the cosine of the angle between them. Values range from -1 (opposite) to 1 @@ -25,12 +24,12 @@ def _l2_distance(vec: list[float]) -> float: Returns: The L2 norm of the vector. + """ norm = 0.0 for element in vec: norm += element * element - norm = math.sqrt(norm) - return norm + return math.sqrt(norm) def cosine_similarity(vec1: list[float], vec2: list[float]) -> float: @@ -49,13 +48,14 @@ def cosine_similarity(vec1: list[float], vec2: list[float]) -> float: Examples: >>> round(cosine_similarity([1, 1, 1], [1, 2, -1]), 15) 0.471404520791032 + """ if len(vec1) != len(vec2): raise ValueError( "The two vectors must be the same length. Got shape " + str(len(vec1)) + " and " - + str(len(vec2)) + + str(len(vec2)), ) norm_a = _l2_distance(vec1) diff --git a/algorithms/math/decimal_to_binary_ip.py b/algorithms/math/decimal_to_binary_ip.py index 6644cd916..ab5822c1f 100644 --- a/algorithms/math/decimal_to_binary_ip.py +++ b/algorithms/math/decimal_to_binary_ip.py @@ -1,5 +1,4 @@ -""" -Decimal to Binary IP Conversion +"""Decimal to Binary IP Conversion. Convert an IP address from dotted-decimal notation to its binary representation. @@ -26,6 +25,7 @@ def decimal_to_binary_util(val: str) -> str: Examples: >>> decimal_to_binary_util('192') '11000000' + """ bits = [128, 64, 32, 16, 8, 4, 2, 1] val_int = int(val) @@ -52,6 +52,7 @@ def decimal_to_binary_ip(ip: str) -> str: Examples: >>> decimal_to_binary_ip('192.168.0.1') '11000000.10101000.00000000.00000001' + """ values = ip.split(".") binary_list = [] diff --git a/algorithms/math/diffie_hellman_key_exchange.py b/algorithms/math/diffie_hellman_key_exchange.py index ea5450266..2586a15b3 100644 --- a/algorithms/math/diffie_hellman_key_exchange.py +++ b/algorithms/math/diffie_hellman_key_exchange.py @@ -1,5 +1,4 @@ -""" -Diffie-Hellman Key Exchange +"""Diffie-Hellman Key Exchange. Implements the Diffie-Hellman key exchange protocol, which enables two parties to establish a shared secret over an insecure channel using discrete @@ -15,6 +14,7 @@ Complexity: Time: O(p) for primitive root finding, O(log(p)) for key generation Space: O(p) for primitive root list + """ from __future__ import annotations @@ -31,10 +31,11 @@ def _prime_check(num: int) -> bool: Returns: True if num is prime, False otherwise. + """ if num <= 1: return False - if num == 2 or num == 3: + if num in {2, 3}: return True if num % 2 == 0 or num % 3 == 0: return False @@ -55,6 +56,7 @@ def _find_order(a: int, n: int) -> int: Returns: The smallest positive k such that a^k = 1 (mod n), or -1 if none exists. + """ if (a == 1) & (n == 1): return 1 @@ -74,6 +76,7 @@ def _euler_totient(n: int) -> int: Returns: The number of integers from 1 to n that are coprime with n. + """ result = n for i in range(2, int(n**0.5) + 1): @@ -94,6 +97,7 @@ def _find_primitive_root(n: int) -> list[int]: Returns: List of all primitive roots of n, or empty list if none exist. + """ if n == 1: return [0] @@ -116,6 +120,7 @@ def alice_private_key(p: int) -> int: Returns: A random private key. + """ return secrets.randbelow(p - 1) + 1 @@ -130,6 +135,7 @@ def alice_public_key(a_pr_k: int, a: int, p: int) -> int: Returns: Alice's public key. + """ return pow(a, a_pr_k, p) @@ -142,6 +148,7 @@ def bob_private_key(p: int) -> int: Returns: A random private key. + """ return secrets.randbelow(p - 1) + 1 @@ -156,6 +163,7 @@ def bob_public_key(b_pr_k: int, a: int, p: int) -> int: Returns: Bob's public key. + """ return pow(a, b_pr_k, p) @@ -170,6 +178,7 @@ def alice_shared_key(b_pu_k: int, a_pr_k: int, p: int) -> int: Returns: The shared secret key. + """ return pow(b_pu_k, a_pr_k, p) @@ -184,6 +193,7 @@ def bob_shared_key(a_pu_k: int, b_pr_k: int, p: int) -> int: Returns: The shared secret key. + """ return pow(a_pu_k, b_pr_k, p) @@ -203,6 +213,7 @@ def diffie_hellman_key_exchange(a: int, p: int, option: int | None = None) -> bo Examples: >>> diffie_hellman_key_exchange(3, 353) True + """ if _prime_check(p) is False: return False diff --git a/algorithms/math/distance_between_two_points.py b/algorithms/math/distance_between_two_points.py index 053d7c9ee..e8ea23037 100644 --- a/algorithms/math/distance_between_two_points.py +++ b/algorithms/math/distance_between_two_points.py @@ -1,5 +1,4 @@ -""" -Distance Between Two Points in 2D Space +"""Distance Between Two Points in 2D Space. Calculate the Euclidean distance between two points using the distance formula derived from the Pythagorean theorem. @@ -33,5 +32,6 @@ def distance_between_two_points(x1: float, y1: float, x2: float, y2: float) -> f 5.0 >>> distance_between_two_points(-1, -1, 2, 3) 5.0 + """ return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) diff --git a/algorithms/math/euler_totient.py b/algorithms/math/euler_totient.py index cb4c33d60..7f5288a53 100644 --- a/algorithms/math/euler_totient.py +++ b/algorithms/math/euler_totient.py @@ -1,5 +1,4 @@ -""" -Euler's Totient Function +"""Euler's Totient Function. Compute Euler's totient function phi(n), which counts the number of integers from 1 to n inclusive that are coprime to n. @@ -28,6 +27,7 @@ def euler_totient(n: int) -> int: 4 >>> euler_totient(21) 12 + """ result = n for i in range(2, int(n**0.5) + 1): diff --git a/algorithms/math/extended_gcd.py b/algorithms/math/extended_gcd.py index cdcd40a0d..54dfd7f56 100644 --- a/algorithms/math/extended_gcd.py +++ b/algorithms/math/extended_gcd.py @@ -1,5 +1,4 @@ -""" -Extended Euclidean Algorithm +"""Extended Euclidean Algorithm. Find coefficients s and t (Bezout's identity) such that: num1 * s + num2 * t = gcd(num1, num2). @@ -29,6 +28,7 @@ def extended_gcd(num1: int, num2: int) -> tuple[int, int, int]: (0, 1, 2) >>> extended_gcd(13, 17) (4, -3, 1) + """ old_s, s = 1, 0 old_t, t = 0, 1 diff --git a/algorithms/math/factorial.py b/algorithms/math/factorial.py index 4edca0f20..dca3dd314 100644 --- a/algorithms/math/factorial.py +++ b/algorithms/math/factorial.py @@ -1,5 +1,4 @@ -""" -Factorial +"""Factorial. Compute the factorial of a non-negative integer, with optional modular arithmetic support. @@ -32,11 +31,14 @@ def factorial(n: int, mod: int | None = None) -> int: 120 >>> factorial(10) 3628800 + """ if not (isinstance(n, int) and n >= 0): - raise ValueError("'n' must be a non-negative integer.") + msg = "'n' must be a non-negative integer." + raise ValueError(msg) if mod is not None and not (isinstance(mod, int) and mod > 0): - raise ValueError("'mod' must be a positive integer") + msg = "'mod' must be a positive integer" + raise ValueError(msg) result = 1 if n == 0: return 1 @@ -63,11 +65,14 @@ def factorial_recur(n: int, mod: int | None = None) -> int: Examples: >>> factorial_recur(5) 120 + """ if not (isinstance(n, int) and n >= 0): - raise ValueError("'n' must be a non-negative integer.") + msg = "'n' must be a non-negative integer." + raise ValueError(msg) if mod is not None and not (isinstance(mod, int) and mod > 0): - raise ValueError("'mod' must be a positive integer") + msg = "'mod' must be a positive integer" + raise ValueError(msg) if n == 0: return 1 result = n * factorial(n - 1, mod) diff --git a/algorithms/math/fft.py b/algorithms/math/fft.py index ea10f6f26..cc0eb20b4 100644 --- a/algorithms/math/fft.py +++ b/algorithms/math/fft.py @@ -1,5 +1,4 @@ -""" -Fast Fourier Transform (Cooley-Tukey) +"""Fast Fourier Transform (Cooley-Tukey). Compute the Discrete Fourier Transform of a sequence using the Cooley-Tukey radix-2 decimation-in-time algorithm. Input length must be a power of 2. @@ -28,6 +27,7 @@ def fft(x: list[complex]) -> list[complex]: Examples: >>> fft([1.0, 1.0, 1.0, 1.0]) [(4+0j), 0j, 0j, 0j] + """ n = len(x) if n == 1: diff --git a/algorithms/math/find_order_simple.py b/algorithms/math/find_order_simple.py index 9e073c43d..4de818cb8 100644 --- a/algorithms/math/find_order_simple.py +++ b/algorithms/math/find_order_simple.py @@ -1,5 +1,4 @@ -""" -Multiplicative Order +"""Multiplicative Order. Find the multiplicative order of a modulo n, which is the smallest positive integer k such that a^k = 1 (mod n). Requires gcd(a, n) = 1. @@ -32,6 +31,7 @@ def find_order(a: int, n: int) -> int: 6 >>> find_order(1, 1) 1 + """ if (a == 1) & (n == 1): return 1 diff --git a/algorithms/math/find_primitive_root_simple.py b/algorithms/math/find_primitive_root_simple.py index d3262b278..fd5082e99 100644 --- a/algorithms/math/find_primitive_root_simple.py +++ b/algorithms/math/find_primitive_root_simple.py @@ -1,5 +1,4 @@ -""" -Primitive Root Finder +"""Primitive Root Finder. Find all primitive roots of a positive integer n. A primitive root modulo n is an integer whose multiplicative order modulo n equals Euler's totient @@ -26,6 +25,7 @@ def _find_order(a: int, n: int) -> int: Returns: The smallest positive k where a^k = 1 (mod n), or -1 if none exists. + """ if (a == 1) & (n == 1): return 1 @@ -45,6 +45,7 @@ def _euler_totient(n: int) -> int: Returns: The count of integers in [1, n] coprime to n. + """ result = n for i in range(2, int(n**0.5) + 1): @@ -72,6 +73,7 @@ def find_primitive_root(n: int) -> list[int]: [2, 3] >>> find_primitive_root(1) [0] + """ if n == 1: return [0] diff --git a/algorithms/math/gcd.py b/algorithms/math/gcd.py index 4d0b1acd3..3db55f4b7 100644 --- a/algorithms/math/gcd.py +++ b/algorithms/math/gcd.py @@ -1,5 +1,4 @@ -""" -Greatest Common Divisor and Least Common Multiple +"""Greatest Common Divisor and Least Common Multiple. Compute the GCD and LCM of two integers using Euclid's algorithm and a bitwise variant. @@ -32,16 +31,19 @@ def gcd(a: int, b: int) -> int: 4 >>> gcd(13, 17) 1 + """ a_int = isinstance(a, int) b_int = isinstance(b, int) a = abs(a) b = abs(b) if not (a_int and b_int): - raise ValueError("Input arguments are not integers") + msg = "Input arguments are not integers" + raise ValueError(msg) if (a == 0) or (b == 0): - raise ValueError("One or more input arguments equals zero") + msg = "One or more input arguments equals zero" + raise ValueError(msg) while b != 0: a, b = b, a % b @@ -61,6 +63,7 @@ def lcm(a: int, b: int) -> int: Examples: >>> lcm(8, 12) 24 + """ return abs(a) * abs(b) // gcd(a, b) @@ -79,6 +82,7 @@ def trailing_zero(x: int) -> int: 1 >>> trailing_zero(40) 3 + """ count = 0 while x and not x & 1: @@ -100,6 +104,7 @@ def gcd_bit(a: int, b: int) -> int: Examples: >>> gcd_bit(8, 12) 4 + """ tza = trailing_zero(a) tzb = trailing_zero(b) diff --git a/algorithms/math/generate_strobogrammtic.py b/algorithms/math/generate_strobogrammtic.py index c0f6beae5..2fe9d0a45 100644 --- a/algorithms/math/generate_strobogrammtic.py +++ b/algorithms/math/generate_strobogrammtic.py @@ -1,5 +1,4 @@ -""" -Generate Strobogrammatic Numbers +"""Generate Strobogrammatic Numbers. A strobogrammatic number looks the same when rotated 180 degrees. Generate all strobogrammatic numbers of a given length or count them within a range. @@ -26,6 +25,7 @@ def gen_strobogrammatic(n: int) -> list[str]: Examples: >>> gen_strobogrammatic(2) ['88', '11', '96', '69'] + """ return _helper(n, n) @@ -39,6 +39,7 @@ def _helper(n: int, length: int) -> list[str]: Returns: List of strobogrammatic number strings. + """ if n == 0: return [""] @@ -69,6 +70,7 @@ def strobogrammatic_in_range(low: str, high: str) -> int: Examples: >>> strobogrammatic_in_range("10", "100") 4 + """ res: list[str] = [] count = 0 @@ -94,6 +96,7 @@ def _helper2(n: int, length: int) -> list[str]: Returns: List of strobogrammatic number strings. + """ if n == 0: return [""] diff --git a/algorithms/math/geometric_mean.py b/algorithms/math/geometric_mean.py index c96f20cfd..b06e8b382 100644 --- a/algorithms/math/geometric_mean.py +++ b/algorithms/math/geometric_mean.py @@ -36,6 +36,7 @@ def geometric_mean(numbers: list[float]) -> float: 1.0 >>> round(geometric_mean([100, 100, 100]), 4) 100.0 + """ if not numbers: msg = "List cannot be empty" @@ -72,6 +73,7 @@ def geometric_mean_logarithm(numbers: list[float]) -> float: 4.0 >>> round(geometric_mean_logarithm([1, 2, 3, 4, 5]), 4) 2.6052 + """ if not numbers: msg = "List cannot be empty" diff --git a/algorithms/math/goldbach.py b/algorithms/math/goldbach.py index 0e28bf45b..78cb7f751 100644 --- a/algorithms/math/goldbach.py +++ b/algorithms/math/goldbach.py @@ -1,5 +1,4 @@ -""" -Goldbach's Conjecture +"""Goldbach's Conjecture. Every even integer greater than 2 can be expressed as the sum of two primes. This module provides a function to find such a pair of primes and a helper @@ -23,6 +22,7 @@ def _is_prime(n: int) -> bool: True >>> _is_prime(10) False + """ if n < 2: return False @@ -59,6 +59,7 @@ def goldbach(n: int) -> tuple[int, int]: >>> p, q = goldbach(100) >>> p + q == 100 and _is_prime(p) and _is_prime(q) True + """ if n <= 2 or n % 2 != 0: msg = f"n must be an even integer greater than 2, got {n}" @@ -88,5 +89,6 @@ def verify_goldbach(limit: int) -> bool: True >>> verify_goldbach(1000) True + """ return all(goldbach(n) is not None for n in range(4, limit + 1, 2)) diff --git a/algorithms/math/hailstone.py b/algorithms/math/hailstone.py index 0b4585703..ff6aab46a 100644 --- a/algorithms/math/hailstone.py +++ b/algorithms/math/hailstone.py @@ -1,5 +1,4 @@ -""" -Hailstone Sequence (Collatz Conjecture) +"""Hailstone Sequence (Collatz Conjecture). Generate the hailstone sequence starting from n: if n is even, next is n/2; if n is odd, next is 3n + 1. The sequence ends when it reaches 1. @@ -28,6 +27,7 @@ def hailstone(n: int) -> list[int]: [8, 4, 2, 1] >>> hailstone(10) [10, 5, 16, 8, 4, 2, 1] + """ sequence = [n] while n > 1: diff --git a/algorithms/math/is_strobogrammatic.py b/algorithms/math/is_strobogrammatic.py index 701a326eb..c234f7814 100644 --- a/algorithms/math/is_strobogrammatic.py +++ b/algorithms/math/is_strobogrammatic.py @@ -1,5 +1,4 @@ -""" -Strobogrammatic Number Check +"""Strobogrammatic Number Check. Determine whether a number (as a string) is strobogrammatic, meaning it looks the same when rotated 180 degrees. @@ -28,6 +27,7 @@ def is_strobogrammatic(num: str) -> bool: True >>> is_strobogrammatic("14") False + """ comb = "00 11 88 69 96" i = 0 @@ -54,5 +54,6 @@ def is_strobogrammatic2(num: str) -> bool: True >>> is_strobogrammatic2("14") False + """ return num == num[::-1].replace("6", "#").replace("9", "6").replace("#", "9") diff --git a/algorithms/math/krishnamurthy_number.py b/algorithms/math/krishnamurthy_number.py index 041497ca5..6fcd02ed4 100644 --- a/algorithms/math/krishnamurthy_number.py +++ b/algorithms/math/krishnamurthy_number.py @@ -1,5 +1,4 @@ -""" -Krishnamurthy Number +"""Krishnamurthy Number. A Krishnamurthy number is a number whose sum of the factorials of its digits equals the number itself (e.g., 145 = 1! + 4! + 5!). @@ -22,6 +21,7 @@ def _find_factorial(n: int) -> int: Returns: The factorial of n. + """ fact = 1 while n != 0: @@ -44,6 +44,7 @@ def krishnamurthy_number(n: int) -> bool: True >>> krishnamurthy_number(357) False + """ if n == 0: return False diff --git a/algorithms/math/linear_regression.py b/algorithms/math/linear_regression.py index 18a08a61d..7170dfad1 100644 --- a/algorithms/math/linear_regression.py +++ b/algorithms/math/linear_regression.py @@ -56,5 +56,5 @@ def rmse(x: list[float], y: list[float]) -> float: n = len(y) return math.sqrt( sum((yi - (slope * xi + intercept)) ** 2 for xi, yi in zip(x, y, strict=False)) - / n + / n, ) diff --git a/algorithms/math/magic_number.py b/algorithms/math/magic_number.py index 6e78391cd..cc40f9cdd 100644 --- a/algorithms/math/magic_number.py +++ b/algorithms/math/magic_number.py @@ -1,5 +1,4 @@ -""" -Magic Number +"""Magic Number. A magic number is a number where recursively summing its digits eventually yields 1. For example, 199 -> 1+9+9=19 -> 1+9=10 -> 1+0=1. @@ -28,6 +27,7 @@ def magic_number(n: int) -> bool: True >>> magic_number(111) False + """ total_sum = 0 diff --git a/algorithms/math/modular_exponential.py b/algorithms/math/modular_exponential.py index f6f1c52a2..1f1262e3c 100644 --- a/algorithms/math/modular_exponential.py +++ b/algorithms/math/modular_exponential.py @@ -1,5 +1,4 @@ -""" -Modular Exponentiation +"""Modular Exponentiation. Compute (base ^ exponent) % mod efficiently using binary exponentiation (repeated squaring). @@ -31,9 +30,11 @@ def modular_exponential(base: int, exponent: int, mod: int) -> int: Examples: >>> modular_exponential(5, 117, 19) 1 + """ if exponent < 0: - raise ValueError("Exponent must be positive.") + msg = "Exponent must be positive." + raise ValueError(msg) base %= mod result = 1 diff --git a/algorithms/math/modular_inverse.py b/algorithms/math/modular_inverse.py index 37cf6221a..31e39b935 100644 --- a/algorithms/math/modular_inverse.py +++ b/algorithms/math/modular_inverse.py @@ -1,5 +1,4 @@ -""" -Modular Multiplicative Inverse +"""Modular Multiplicative Inverse. Find x such that a * x = 1 (mod m) using the Extended Euclidean Algorithm. Requires a and m to be coprime. @@ -23,6 +22,7 @@ def _extended_gcd(a: int, b: int) -> tuple[int, int, int]: Returns: A tuple (s, t, g) where a * s + b * t = g = gcd(a, b). + """ old_s, s = 1, 0 old_t, t = 0, 1 @@ -54,8 +54,10 @@ def modular_inverse(a: int, m: int) -> int: Examples: >>> modular_inverse(2, 19) 10 + """ s, _, g = _extended_gcd(a, m) if g != 1: - raise ValueError("a and m must be coprime") + msg = "a and m must be coprime" + raise ValueError(msg) return s % m diff --git a/algorithms/math/next_bigger.py b/algorithms/math/next_bigger.py index 061071052..08ec7aa6c 100644 --- a/algorithms/math/next_bigger.py +++ b/algorithms/math/next_bigger.py @@ -1,5 +1,4 @@ -""" -Next Bigger Number with Same Digits +"""Next Bigger Number with Same Digits. Given a number, find the next higher number that uses the exact same set of digits. This is equivalent to finding the next permutation. @@ -29,6 +28,7 @@ def next_bigger(num: int) -> int: 38627 >>> next_bigger(99999) -1 + """ digits = [int(i) for i in str(num)] idx = len(digits) - 1 diff --git a/algorithms/math/next_perfect_square.py b/algorithms/math/next_perfect_square.py index 1efa124c1..ab49ac355 100644 --- a/algorithms/math/next_perfect_square.py +++ b/algorithms/math/next_perfect_square.py @@ -1,5 +1,4 @@ -""" -Next Perfect Square +"""Next Perfect Square. Given a number, find the next perfect square if the input is itself a perfect square. Otherwise, return -1. @@ -28,6 +27,7 @@ def find_next_square(sq: float) -> int: 144 >>> find_next_square(10) -1 + """ root = sq**0.5 if root.is_integer(): @@ -49,6 +49,7 @@ def find_next_square2(sq: float) -> int: 144 >>> find_next_square2(10) -1 + """ root = sq**0.5 return -1 if root % 1 else int((root + 1) ** 2) diff --git a/algorithms/math/nth_digit.py b/algorithms/math/nth_digit.py index efa91b551..f06cdecfc 100644 --- a/algorithms/math/nth_digit.py +++ b/algorithms/math/nth_digit.py @@ -1,5 +1,4 @@ -""" -Find the Nth Digit +"""Find the Nth Digit. Find the nth digit in the infinite sequence 1, 2, 3, ..., 9, 10, 11, 12, ... by determining which number contains it and extracting the specific digit. @@ -26,6 +25,7 @@ def find_nth_digit(n: int) -> int: Examples: >>> find_nth_digit(11) 0 + """ length = 1 count = 9 diff --git a/algorithms/math/num_digits.py b/algorithms/math/num_digits.py index a37268392..97d0cef63 100644 --- a/algorithms/math/num_digits.py +++ b/algorithms/math/num_digits.py @@ -1,5 +1,4 @@ -""" -Number of Digits +"""Number of Digits. Count the number of digits in an integer using logarithmic computation for O(1) time complexity. @@ -32,6 +31,7 @@ def num_digits(n: int) -> int: 1 >>> num_digits(-254) 3 + """ n = abs(n) if n == 0: diff --git a/algorithms/math/num_perfect_squares.py b/algorithms/math/num_perfect_squares.py index 6a65f06bb..f8ca8fe6f 100644 --- a/algorithms/math/num_perfect_squares.py +++ b/algorithms/math/num_perfect_squares.py @@ -1,5 +1,4 @@ -""" -Minimum Perfect Squares Sum +"""Minimum Perfect Squares Sum. Determine the minimum number of perfect squares that sum to a given integer. By Lagrange's four-square theorem, the answer is always between 1 and 4. @@ -34,6 +33,7 @@ def num_perfect_squares(number: int) -> int: 3 >>> num_perfect_squares(31) 4 + """ if int(math.sqrt(number)) ** 2 == number: return 1 diff --git a/algorithms/math/polynomial.py b/algorithms/math/polynomial.py index 42fefe4b5..ac18c10ee 100644 --- a/algorithms/math/polynomial.py +++ b/algorithms/math/polynomial.py @@ -1,5 +1,4 @@ -""" -Polynomial and Monomial Arithmetic +"""Polynomial and Monomial Arithmetic. A symbolic algebra system for polynomials and monomials supporting addition, subtraction, multiplication, division, substitution, and polynomial long @@ -14,17 +13,20 @@ from __future__ import annotations -from collections.abc import Iterable from fractions import Fraction from functools import reduce from numbers import Rational +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterable class Monomial: """A monomial represented by a coefficient and variable-to-power mapping.""" def __init__( - self, variables: dict[int, int], coeff: int | float | Fraction | None = None + self, variables: dict[int, int], coeff: float | Fraction | None = None, ) -> None: """Create a monomial with the given variables and coefficient. @@ -35,8 +37,9 @@ def __init__( Examples: >>> _ = Monomial({1: 1}) # (a_1)^1 >>> _ = Monomial({1: 3, 2: 2}, 12) # 12(a_1)^3(a_2)^2 + """ - self.variables = dict() + self.variables = {} if coeff is None: coeff = Fraction(0, 1) if len(variables) == 0 else Fraction(1, 1) @@ -48,14 +51,14 @@ def __init__( self.coeff = Monomial._rationalize_if_possible(coeff) return - for i in variables: - if variables[i] != 0: - self.variables[i] = variables[i] + for i, v in variables.items(): + if v != 0: + self.variables[i] = v self.coeff = Monomial._rationalize_if_possible(coeff) @staticmethod def _rationalize_if_possible( - num: int | float | Fraction, + num: float | Fraction, ) -> Fraction | float: """Convert numbers to Fraction when possible. @@ -64,12 +67,12 @@ def _rationalize_if_possible( Returns: A Fraction if the input is Rational, otherwise the original value. + """ if isinstance(num, Rational): res = Fraction(num, 1) return Fraction(res.numerator, res.denominator) - else: - return num + return num def equal_upto_scalar(self, other: object) -> bool: """Check if other is a monomial equivalent to self up to scalar multiple. @@ -82,12 +85,14 @@ def equal_upto_scalar(self, other: object) -> bool: Raises: ValueError: If other is not a Monomial. + """ if not isinstance(other, Monomial): - raise ValueError("Can only compare monomials.") + msg = "Can only compare monomials." + raise ValueError(msg) return other.variables == self.variables - def __add__(self, other: int | float | Fraction) -> Monomial: + def __add__(self, other: float | Fraction) -> Monomial: """Add two monomials or a monomial with a scalar. Args: @@ -98,23 +103,28 @@ def __add__(self, other: int | float | Fraction) -> Monomial: Raises: ValueError: If monomials have different variables. + """ if isinstance(other, (int, float, Fraction)): return self.__add__(Monomial({}, Monomial._rationalize_if_possible(other))) if not isinstance(other, Monomial): - raise ValueError("Can only add monomials, ints, floats, or Fractions.") + msg = "Can only add monomials, ints, floats, or Fractions." + raise ValueError(msg) if self.variables == other.variables: mono = {i: self.variables[i] for i in self.variables} return Monomial( - mono, Monomial._rationalize_if_possible(self.coeff + other.coeff) + mono, Monomial._rationalize_if_possible(self.coeff + other.coeff), ).clean() - raise ValueError( - f"Cannot add {str(other)} to {self.__str__()} " + msg = ( + f"Cannot add {other!s} to {self.__str__()} " "because they don't have same variables." ) + raise ValueError( + msg, + ) def __eq__(self, other: object) -> bool: """Check equality of two monomials. @@ -124,12 +134,13 @@ def __eq__(self, other: object) -> bool: Returns: True if both monomials are equal. + """ if not isinstance(other, Monomial): return NotImplemented return self.equal_upto_scalar(other) and self.coeff == other.coeff - def __mul__(self, other: int | float | Fraction) -> Monomial: + def __mul__(self, other: float | Fraction) -> Monomial: """Multiply two monomials or a monomial with a scalar. Args: @@ -140,31 +151,32 @@ def __mul__(self, other: int | float | Fraction) -> Monomial: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (float, int, Fraction)): mono = {i: self.variables[i] for i in self.variables} return Monomial( - mono, Monomial._rationalize_if_possible(self.coeff * other) + mono, Monomial._rationalize_if_possible(self.coeff * other), ).clean() if not isinstance(other, Monomial): - raise ValueError("Can only multiply monomials, ints, floats, or Fractions.") - else: - mono = {i: self.variables[i] for i in self.variables} - for i in other.variables: - if i in mono: - mono[i] += other.variables[i] - else: - mono[i] = other.variables[i] + msg = "Can only multiply monomials, ints, floats, or Fractions." + raise ValueError(msg) + mono = {i: self.variables[i] for i in self.variables} + for i in other.variables: + if i in mono: + mono[i] += other.variables[i] + else: + mono[i] = other.variables[i] - temp = dict() - for k in mono: - if mono[k] != 0: - temp[k] = mono[k] + temp = {} + for k in mono: + if mono[k] != 0: + temp[k] = mono[k] - return Monomial( - temp, Monomial._rationalize_if_possible(self.coeff * other.coeff) - ).clean() + return Monomial( + temp, Monomial._rationalize_if_possible(self.coeff * other.coeff), + ).clean() def inverse(self) -> Monomial: """Compute the multiplicative inverse of this monomial. @@ -174,15 +186,17 @@ def inverse(self) -> Monomial: Raises: ValueError: If the coefficient is zero. + """ mono = {i: self.variables[i] for i in self.variables if self.variables[i] != 0} for i in mono: mono[i] *= -1 if self.coeff == 0: - raise ValueError("Coefficient must not be 0.") + msg = "Coefficient must not be 0." + raise ValueError(msg) return Monomial(mono, Monomial._rationalize_if_possible(1 / self.coeff)).clean() - def __truediv__(self, other: int | float | Fraction) -> Monomial: + def __truediv__(self, other: float | Fraction) -> Monomial: """Divide this monomial by another monomial or scalar. Args: @@ -193,19 +207,21 @@ def __truediv__(self, other: int | float | Fraction) -> Monomial: Raises: ValueError: If dividing by zero. + """ if isinstance(other, (int, float, Fraction)): mono = {i: self.variables[i] for i in self.variables} if other == 0: - raise ValueError("Cannot divide by 0.") + msg = "Cannot divide by 0." + raise ValueError(msg) return Monomial( - mono, Monomial._rationalize_if_possible(self.coeff / other) + mono, Monomial._rationalize_if_possible(self.coeff / other), ).clean() o = other.inverse() return self.__mul__(o) - def __floordiv__(self, other: int | float | Fraction) -> Monomial: + def __floordiv__(self, other: float | Fraction) -> Monomial: """Floor division (same as true division for monomials). Args: @@ -213,6 +229,7 @@ def __floordiv__(self, other: int | float | Fraction) -> Monomial: Returns: The resulting Monomial. + """ return self.__truediv__(other) @@ -221,10 +238,11 @@ def clone(self) -> Monomial: Returns: A new Monomial with the same variables and coefficient. + """ temp_variables = {i: self.variables[i] for i in self.variables} return Monomial( - temp_variables, Monomial._rationalize_if_possible(self.coeff) + temp_variables, Monomial._rationalize_if_possible(self.coeff), ).clean() def clean(self) -> Monomial: @@ -232,13 +250,14 @@ def clean(self) -> Monomial: Returns: A cleaned Monomial. + """ temp_variables = { i: self.variables[i] for i in self.variables if self.variables[i] != 0 } return Monomial(temp_variables, Monomial._rationalize_if_possible(self.coeff)) - def __sub__(self, other: int | float | Fraction) -> Monomial: + def __sub__(self, other: float | Fraction) -> Monomial: """Subtract a value from this monomial. Args: @@ -249,17 +268,20 @@ def __sub__(self, other: int | float | Fraction) -> Monomial: Raises: ValueError: If monomials have different variables. + """ if isinstance(other, (int, float, Fraction)): mono = { i: self.variables[i] for i in self.variables if self.variables[i] != 0 } if len(mono) != 0: - raise ValueError("Can only subtract like monomials.") + msg = "Can only subtract like monomials." + raise ValueError(msg) other_term = Monomial(mono, Monomial._rationalize_if_possible(other)) return self.__sub__(other_term) if not isinstance(other, Monomial): - raise ValueError("Can only subtract monomials") + msg = "Can only subtract monomials" + raise ValueError(msg) return self.__add__(other.__mul__(Fraction(-1, 1))) def __hash__(self) -> int: @@ -267,6 +289,7 @@ def __hash__(self) -> int: Returns: An integer hash value. + """ arr = [] for i in sorted(self.variables): @@ -280,12 +303,13 @@ def all_variables(self) -> set: Returns: A set of variable indices. + """ - return set(sorted(self.variables.keys())) + return set(self.variables.keys()) def substitute( self, - substitutions: int | float | Fraction | dict[int, int | float | Fraction], + substitutions: float | Fraction | dict[int, int | float | Fraction], ) -> Fraction: """Evaluate the monomial by substituting values for variables. @@ -298,21 +322,22 @@ def substitute( Raises: ValueError: If some variables are not given values. + """ if isinstance(substitutions, (int, float, Fraction)): substitutions = { v: Monomial._rationalize_if_possible(substitutions) for v in self.all_variables() } - else: - if not self.all_variables().issubset(set(substitutions.keys())): - raise ValueError("Some variables didn't receive their values.") + elif not self.all_variables().issubset(set(substitutions.keys())): + msg = "Some variables didn't receive their values." + raise ValueError(msg) if self.coeff == 0: return Fraction(0, 1) ans = Monomial._rationalize_if_possible(self.coeff) for k in self.variables: ans *= Monomial._rationalize_if_possible( - substitutions[k] ** self.variables[k] + substitutions[k] ** self.variables[k], ) return Monomial._rationalize_if_possible(ans) @@ -321,6 +346,7 @@ def __str__(self) -> str: Returns: A human-readable string. + """ if len(self.variables) == 0: return str(self.coeff) @@ -328,7 +354,7 @@ def __str__(self) -> str: result = str(self.coeff) result += "(" for i in self.variables: - temp = f"a_{str(i)}" + temp = f"a_{i!s}" if self.variables[i] > 1: temp = "(" + temp + f")**{self.variables[i]}" elif self.variables[i] < 0: @@ -345,7 +371,7 @@ class Polynomial: """A polynomial represented as a set of Monomial terms.""" def __init__( - self, monomials: Iterable[int | float | Fraction | Monomial] + self, monomials: Iterable[int | float | Fraction | Monomial], ) -> None: """Create a polynomial from an iterable of monomials or scalars. @@ -354,22 +380,24 @@ def __init__( Raises: ValueError: If an element is not a valid type. + """ self.monomials: set = set() for m in monomials: - if any(map(lambda x: isinstance(m, x), [int, float, Fraction])): + if any(isinstance(m, x) for x in [int, float, Fraction]): self.monomials |= {Monomial({}, m)} elif isinstance(m, Monomial): self.monomials |= {m} else: + msg = "Iterable should have monomials, int, float, or Fraction." raise ValueError( - "Iterable should have monomials, int, float, or Fraction." + msg, ) self.monomials -= {Monomial({}, 0)} @staticmethod def _rationalize_if_possible( - num: int | float | Fraction, + num: float | Fraction, ) -> Fraction | float: """Convert numbers to Fraction when possible. @@ -378,14 +406,14 @@ def _rationalize_if_possible( Returns: A Fraction if the input is Rational, otherwise the original value. + """ if isinstance(num, Rational): res = Fraction(num, 1) return Fraction(res.numerator, res.denominator) - else: - return num + return num - def __add__(self, other: int | float | Fraction | Monomial) -> Polynomial: + def __add__(self, other: float | Fraction | Monomial) -> Polynomial: """Add a polynomial, monomial, or scalar to this polynomial. Args: @@ -396,12 +424,13 @@ def __add__(self, other: int | float | Fraction | Monomial) -> Polynomial: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (int, float, Fraction)): return self.__add__( - Monomial({}, Polynomial._rationalize_if_possible(other)) + Monomial({}, Polynomial._rationalize_if_possible(other)), ) - elif isinstance(other, Monomial): + if isinstance(other, Monomial): monos = {m.clone() for m in self.monomials} for _own_monos in monos: @@ -413,26 +442,28 @@ def __add__(self, other: int | float | Fraction | Monomial) -> Polynomial: Monomial( temp_variables, Polynomial._rationalize_if_possible(scalar + other.coeff), - ) + ), } - return Polynomial([z for z in monos]) + return Polynomial(list(monos)) monos |= {other.clone()} - return Polynomial([z for z in monos]) - elif isinstance(other, Polynomial): - temp = list(z for z in {m.clone() for m in self.all_monomials()}) + return Polynomial(list(monos)) + if isinstance(other, Polynomial): + temp = list({m.clone() for m in self.all_monomials()}) p = Polynomial(temp) for o in other.all_monomials(): p = p.__add__(o.clone()) return p - else: - raise ValueError( - "Can only add int, float, Fraction, Monomials, " - "or Polynomials to Polynomials." - ) + msg = ( + "Can only add int, float, Fraction, Monomials, " + "or Polynomials to Polynomials." + ) + raise ValueError( + msg, + ) - def __sub__(self, other: int | float | Fraction | Monomial) -> Polynomial: + def __sub__(self, other: float | Fraction | Monomial) -> Polynomial: """Subtract a polynomial, monomial, or scalar from this polynomial. Args: @@ -443,12 +474,13 @@ def __sub__(self, other: int | float | Fraction | Monomial) -> Polynomial: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (int, float, Fraction)): return self.__sub__( - Monomial({}, Polynomial._rationalize_if_possible(other)) + Monomial({}, Polynomial._rationalize_if_possible(other)), ) - elif isinstance(other, Monomial): + if isinstance(other, Monomial): monos = {m.clone() for m in self.all_monomials()} for _own_monos in monos: if _own_monos.equal_upto_scalar(other): @@ -459,29 +491,31 @@ def __sub__(self, other: int | float | Fraction | Monomial) -> Polynomial: Monomial( temp_variables, Polynomial._rationalize_if_possible(scalar - other.coeff), - ) + ), } - return Polynomial([z for z in monos]) + return Polynomial(list(monos)) to_insert = other.clone() to_insert.coeff *= -1 monos |= {to_insert} - return Polynomial([z for z in monos]) + return Polynomial(list(monos)) - elif isinstance(other, Polynomial): - p = Polynomial(list(z for z in {m.clone() for m in self.all_monomials()})) + if isinstance(other, Polynomial): + p = Polynomial(list({m.clone() for m in self.all_monomials()})) for o in other.all_monomials(): p = p.__sub__(o.clone()) return p - else: - raise ValueError( - "Can only subtract int, float, Fraction, " - "Monomials, or Polynomials from Polynomials." - ) + msg = ( + "Can only subtract int, float, Fraction, " + "Monomials, or Polynomials from Polynomials." + ) + raise ValueError( + msg, + ) - def __mul__(self, other: int | float | Fraction | Monomial) -> Polynomial: + def __mul__(self, other: float | Fraction | Monomial) -> Polynomial: """Multiply this polynomial by another polynomial, monomial, or scalar. Args: @@ -492,6 +526,7 @@ def __mul__(self, other: int | float | Fraction | Monomial) -> Polynomial: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (int, float, Fraction, Monomial)): result = Polynomial([]) @@ -499,7 +534,7 @@ def __mul__(self, other: int | float | Fraction | Monomial) -> Polynomial: for m in monos: result = result.__add__(m.clone() * other) return result - elif isinstance(other, Polynomial): + if isinstance(other, Polynomial): temp_self = {m.clone() for m in self.all_monomials()} temp_other = {m.clone() for m in other.all_monomials()} @@ -510,13 +545,15 @@ def __mul__(self, other: int | float | Fraction | Monomial) -> Polynomial: result = result.__add__(i * j) return result - else: - raise ValueError( - "Can only multiple int, float, Fraction, " - "Monomials, or Polynomials with Polynomials." - ) + msg = ( + "Can only multiple int, float, Fraction, " + "Monomials, or Polynomials with Polynomials." + ) + raise ValueError( + msg, + ) - def __floordiv__(self, other: int | float | Fraction | Monomial) -> Polynomial: + def __floordiv__(self, other: float | Fraction | Monomial) -> Polynomial: """Floor division (same as true division for polynomials). Args: @@ -524,10 +561,11 @@ def __floordiv__(self, other: int | float | Fraction | Monomial) -> Polynomial: Returns: The resulting Polynomial. + """ return self.__truediv__(other) - def __truediv__(self, other: int | float | Fraction | Monomial) -> Polynomial: + def __truediv__(self, other: float | Fraction | Monomial) -> Polynomial: """Divide this polynomial by another value. Args: @@ -538,30 +576,34 @@ def __truediv__(self, other: int | float | Fraction | Monomial) -> Polynomial: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (int, float, Fraction)): return self.__truediv__(Monomial({}, other)) - elif isinstance(other, Monomial): - poly_temp = reduce( + if isinstance(other, Monomial): + return reduce( lambda acc, val: acc + val, - map(lambda x: x / other, [z for z in self.all_monomials()]), + (x / other for x in list(self.all_monomials())), Polynomial([Monomial({}, 0)]), ) - return poly_temp - elif isinstance(other, Polynomial): - quotient, remainder = self.poly_long_division(other) + if isinstance(other, Polynomial): + quotient, _remainder = self.poly_long_division(other) return quotient - raise ValueError( + msg = ( "Can only divide a polynomial by an int, float, " "Fraction, Monomial, or Polynomial." ) + raise ValueError( + msg, + ) def clone(self) -> Polynomial: """Create a deep copy of this polynomial. Returns: A new Polynomial with cloned monomials. + """ return Polynomial(list({m.clone() for m in self.all_monomials()})) @@ -570,10 +612,11 @@ def variables(self) -> set: Returns: A set of variable indices. + """ res = set() for i in self.all_monomials(): - res |= {j for j in i.variables} + res |= set(i.variables) res = list(res) return set(res) @@ -582,6 +625,7 @@ def all_monomials(self) -> Iterable[Monomial]: Returns: A set of Monomial terms. + """ return {m for m in self.monomials if m != Monomial({}, 0)} @@ -596,23 +640,26 @@ def __eq__(self, other: object) -> bool: Raises: ValueError: If other is not a valid type. + """ if isinstance(other, (int, float, Fraction)): other_poly = Polynomial([Monomial({}, other)]) return self.__eq__(other_poly) - elif isinstance(other, Monomial): + if isinstance(other, Monomial): return self.__eq__(Polynomial([other])) - elif isinstance(other, Polynomial): + if isinstance(other, Polynomial): return self.all_monomials() == other.all_monomials() - else: - raise ValueError( - "Can only compare a polynomial with an int, " - "float, Fraction, Monomial, or another Polynomial." - ) + msg = ( + "Can only compare a polynomial with an int, " + "float, Fraction, Monomial, or another Polynomial." + ) + raise ValueError( + msg, + ) def subs( self, - substitutions: int | float | Fraction | dict[int, int | float | Fraction], + substitutions: float | Fraction | dict[int, int | float | Fraction], ) -> int | float | Fraction: """Evaluate the polynomial by substituting values for variables. @@ -625,6 +672,7 @@ def subs( Raises: ValueError: If some variables are not given values. + """ if isinstance(substitutions, (int, float, Fraction)): substitutions = { @@ -632,10 +680,12 @@ def subs( for i in set(self.variables()) } return self.subs(substitutions) - elif not isinstance(substitutions, dict): - raise ValueError("The substitutions should be a dictionary.") + if not isinstance(substitutions, dict): + msg = "The substitutions should be a dictionary." + raise ValueError(msg) if not self.variables().issubset(set(substitutions.keys())): - raise ValueError("Some variables didn't receive their values.") + msg = "Some variables didn't receive their values." + raise ValueError(msg) ans = 0 for m in self.all_monomials(): @@ -647,6 +697,7 @@ def __str__(self) -> str: Returns: A human-readable string. + """ sorted_monos = sorted( self.all_monomials(), @@ -666,12 +717,15 @@ def poly_long_division(self, other: Polynomial) -> tuple[Polynomial, Polynomial] Raises: ValueError: If other is not a Polynomial or is zero. + """ if not isinstance(other, Polynomial): - raise ValueError("Can only divide by another Polynomial.") + msg = "Can only divide by another Polynomial." + raise ValueError(msg) if len(other.all_monomials()) == 0: - raise ValueError("Cannot divide by zero polynomial.") + msg = "Cannot divide by zero polynomial." + raise ValueError(msg) quotient = Polynomial([]) remainder = self.clone() @@ -684,7 +738,7 @@ def poly_long_division(self, other: Polynomial) -> tuple[Polynomial, Polynomial] divisor_lead = divisor_monos[0] while remainder.all_monomials() and max( - remainder.variables(), default=-1 + remainder.variables(), default=-1, ) >= max(other.variables(), default=-1): remainder_monos = sorted( remainder.all_monomials(), diff --git a/algorithms/math/polynomial_division.py b/algorithms/math/polynomial_division.py index 58e958e23..6f936622f 100644 --- a/algorithms/math/polynomial_division.py +++ b/algorithms/math/polynomial_division.py @@ -11,7 +11,7 @@ def polynomial_division( - dividend: list[float], divisor: list[float] + dividend: list[float], divisor: list[float], ) -> tuple[list[float], list[float]]: """Perform polynomial long division. diff --git a/algorithms/math/power.py b/algorithms/math/power.py index 2bf3f88ad..84c8e8a1b 100644 --- a/algorithms/math/power.py +++ b/algorithms/math/power.py @@ -1,5 +1,4 @@ -""" -Binary Exponentiation +"""Binary Exponentiation. Compute a^n efficiently using binary exponentiation (exponentiation by squaring), with optional modular arithmetic. @@ -30,6 +29,7 @@ def power(a: int, n: int, mod: int | None = None) -> int: 8 >>> power(10, 3, 5) 0 + """ ans = 1 while n: @@ -57,6 +57,7 @@ def power_recur(a: int, n: int, mod: int | None = None) -> int: Examples: >>> power_recur(2, 3) 8 + """ if n == 0: ans = 1 diff --git a/algorithms/math/prime_check.py b/algorithms/math/prime_check.py index abad8431a..fab47280b 100644 --- a/algorithms/math/prime_check.py +++ b/algorithms/math/prime_check.py @@ -1,5 +1,4 @@ -""" -Primality Test +"""Primality Test. Check whether a given integer is prime using trial division with 6k +/- 1 optimization. @@ -28,10 +27,11 @@ def prime_check(n: int) -> bool: True >>> prime_check(4) False + """ if n <= 1: return False - if n == 2 or n == 3: + if n in {2, 3}: return True if n % 2 == 0 or n % 3 == 0: return False diff --git a/algorithms/math/primes_sieve_of_eratosthenes.py b/algorithms/math/primes_sieve_of_eratosthenes.py index 901b6bcb3..eaf53b710 100644 --- a/algorithms/math/primes_sieve_of_eratosthenes.py +++ b/algorithms/math/primes_sieve_of_eratosthenes.py @@ -1,5 +1,4 @@ -""" -Sieve of Eratosthenes +"""Sieve of Eratosthenes. Generate all prime numbers less than n using an optimized sieve that skips even numbers. @@ -29,9 +28,11 @@ def get_primes(n: int) -> list[int]: Examples: >>> get_primes(7) [2, 3, 5, 7] + """ if n <= 0: - raise ValueError("'n' must be a positive integer.") + msg = "'n' must be a positive integer." + raise ValueError(msg) sieve_size = (n // 2 - 1) if n % 2 == 0 else (n // 2) sieve = [True for _ in range(sieve_size)] primes: list[int] = [] diff --git a/algorithms/math/pythagoras.py b/algorithms/math/pythagoras.py index 17c701b2b..bac4a4cc5 100644 --- a/algorithms/math/pythagoras.py +++ b/algorithms/math/pythagoras.py @@ -1,5 +1,4 @@ -""" -Pythagorean Theorem +"""Pythagorean Theorem. Given the lengths of two sides of a right-angled triangle, compute the length of the third side using the Pythagorean theorem. @@ -15,7 +14,7 @@ def pythagoras( - opposite: float | str, adjacent: float | str, hypotenuse: float | str + opposite: float | str, adjacent: float | str, hypotenuse: float | str, ) -> str: """Compute the unknown side of a right triangle. @@ -35,6 +34,7 @@ def pythagoras( Examples: >>> pythagoras(3, 4, "?") 'Hypotenuse = 5.0' + """ try: if opposite == "?": @@ -45,4 +45,5 @@ def pythagoras( return "Hypotenuse = " + str(((opposite**2) + (adjacent**2)) ** 0.5) return "You already know the answer!" except Exception as err: - raise ValueError("invalid argument(s) were given.") from err + msg = "invalid argument(s) were given." + raise ValueError(msg) from err diff --git a/algorithms/math/rabin_miller.py b/algorithms/math/rabin_miller.py index 22725f155..bd94bb775 100644 --- a/algorithms/math/rabin_miller.py +++ b/algorithms/math/rabin_miller.py @@ -1,5 +1,4 @@ -""" -Rabin-Miller Primality Test +"""Rabin-Miller Primality Test. A probabilistic primality test where returning False guarantees the number is composite, and returning True means the number is probably prime with @@ -32,6 +31,7 @@ def is_prime(n: int, k: int) -> bool: True >>> is_prime(6, 2) False + """ def _pow2_factor(num: int) -> tuple[int, int]: @@ -42,6 +42,7 @@ def _pow2_factor(num: int) -> tuple[int, int]: Returns: A tuple (power, odd_part). + """ power = 0 while num % 2 == 0: @@ -57,6 +58,7 @@ def _valid_witness(a: int) -> bool: Returns: True if a proves n is composite, False otherwise. + """ x = pow(int(a), int(d), int(n)) @@ -74,7 +76,7 @@ def _valid_witness(a: int) -> bool: return True if n < 5: - return n == 2 or n == 3 + return n in {2, 3} r, d = _pow2_factor(n - 1) diff --git a/algorithms/math/recursive_binomial_coefficient.py b/algorithms/math/recursive_binomial_coefficient.py index aefe20d0b..8f2a0975d 100644 --- a/algorithms/math/recursive_binomial_coefficient.py +++ b/algorithms/math/recursive_binomial_coefficient.py @@ -1,5 +1,4 @@ -""" -Recursive Binomial Coefficient +"""Recursive Binomial Coefficient. Calculate the binomial coefficient C(n, k) using a recursive formula with the identity C(n, k) = (n/k) * C(n-1, k-1). @@ -32,10 +31,12 @@ def recursive_binomial_coefficient(n: int, k: int) -> int: 1 >>> recursive_binomial_coefficient(8, 2) 28 + """ if k > n: - raise ValueError("Invalid Inputs, ensure that n >= k") - if k == 0 or n == k: + msg = "Invalid Inputs, ensure that n >= k" + raise ValueError(msg) + if k in (0, n): return 1 if k > n / 2: return recursive_binomial_coefficient(n, n - k) diff --git a/algorithms/math/rsa.py b/algorithms/math/rsa.py index 19df24929..8d3a5a33c 100644 --- a/algorithms/math/rsa.py +++ b/algorithms/math/rsa.py @@ -1,5 +1,4 @@ -""" -RSA Encryption Algorithm +"""RSA Encryption Algorithm. Implements RSA key generation, encryption, and decryption. RSA uses separate public and private keys where ((x^e)^d) % n == x % n. @@ -14,6 +13,7 @@ Complexity: Time: O(k^3) for key generation (k = bit length) Space: O(k) + """ from __future__ import annotations @@ -36,7 +36,8 @@ def _extended_gcd(a: int, b: int) -> tuple[int, int, int]: def _modinv(a: int, m: int) -> int: g, x, _ = _extended_gcd(a, m) if g != 1: - raise ValueError(f"Modular inverse does not exist: gcd({a}, {m}) = {g}") + msg = f"Modular inverse does not exist: gcd({a}, {m}) = {g}" + raise ValueError(msg) return x % m @@ -54,6 +55,7 @@ def generate_key(k: int, seed: int | None = None) -> tuple[int, int, int]: Examples: >>> n, e, d = generate_key(16) + """ def _gen_prime(k: int, seed: int | None = None) -> int: @@ -65,6 +67,7 @@ def _gen_prime(k: int, seed: int | None = None) -> int: Returns: A prime number. + """ def _is_prime(num: int) -> bool: @@ -116,6 +119,7 @@ def encrypt(data: int, e: int, n: int) -> int: Examples: >>> encrypt(7, 23, 143) 2 + """ return pow(int(data), int(e), int(n)) @@ -134,5 +138,6 @@ def decrypt(data: int, d: int, n: int) -> int: Examples: >>> decrypt(2, 47, 143) 7 + """ return pow(int(data), int(d), int(n)) diff --git a/algorithms/math/sqrt_precision_factor.py b/algorithms/math/sqrt_precision_factor.py index ff0268cb8..bb55378a3 100644 --- a/algorithms/math/sqrt_precision_factor.py +++ b/algorithms/math/sqrt_precision_factor.py @@ -1,5 +1,4 @@ -""" -Square Root by Newton's Method +"""Square Root by Newton's Method. Compute the square root of a positive number using Newton's method (Babylonian method) with a configurable precision factor. @@ -27,6 +26,7 @@ def square_root(n: float, epsilon: float = 0.001) -> float: Examples: >>> abs(square_root(5, 0.001) - 2.236) < 0.002 True + """ guess = n / 2 diff --git a/algorithms/math/summing_digits.py b/algorithms/math/summing_digits.py index 1b67c983d..6a8a0062b 100644 --- a/algorithms/math/summing_digits.py +++ b/algorithms/math/summing_digits.py @@ -1,5 +1,4 @@ -""" -Summing Digits Power +"""Summing Digits Power. Find all integers in a range where each digit raised to its positional power (1-indexed) sums to the number itself (e.g., 89 = 8^1 + 9^2). @@ -29,6 +28,7 @@ def sum_dig_pow(low: int, high: int) -> list[int]: [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> sum_dig_pow(1, 100) [1, 2, 3, 4, 5, 6, 7, 8, 9, 89] + """ result = [] diff --git a/algorithms/math/surface_area_of_torus.py b/algorithms/math/surface_area_of_torus.py index 07bd84920..70993a3b8 100644 --- a/algorithms/math/surface_area_of_torus.py +++ b/algorithms/math/surface_area_of_torus.py @@ -1,5 +1,4 @@ -""" -Surface Area of a Torus +"""Surface Area of a Torus. Calculate the surface area of a torus given its major and minor radii using the formula A = 4 * pi^2 * R * r. @@ -33,8 +32,10 @@ def surface_area_of_torus(major_radius: float, minor_radius: float) -> float: Examples: >>> surface_area_of_torus(3.0, 1.0) 118.4352528130723 + """ if major_radius < 0 or minor_radius < 0: - raise ValueError("Radii must be non-negative") + msg = "Radii must be non-negative" + raise ValueError(msg) return 4 * pi**2 * major_radius * minor_radius diff --git a/algorithms/math/symmetry_group_cycle_index.py b/algorithms/math/symmetry_group_cycle_index.py index 0f026c94a..94a3679e1 100644 --- a/algorithms/math/symmetry_group_cycle_index.py +++ b/algorithms/math/symmetry_group_cycle_index.py @@ -1,5 +1,4 @@ -""" -Symmetry Group Cycle Index +"""Symmetry Group Cycle Index. Compute the cycle index polynomial of the symmetric group S_n and use it to count distinct configurations of grids under row/column permutations @@ -29,11 +28,13 @@ def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: Returns: The resultant monomial from the Cartesian product merging. + """ - assert isinstance(m1, Monomial) and isinstance(m2, Monomial) + assert isinstance(m1, Monomial) + assert isinstance(m2, Monomial) a_vars = m1.variables b_vars = m2.variables - result_variables: dict[int, int] = dict() + result_variables: dict[int, int] = {} for i in a_vars: for j in b_vars: k = lcm(i, j) @@ -47,7 +48,7 @@ def cycle_product(m1: Monomial, m2: Monomial) -> Monomial: def cycle_product_for_two_polynomials( - p1: Polynomial, p2: Polynomial, q: float | int | Fraction + p1: Polynomial, p2: Polynomial, q: float | Fraction, ) -> float | int | Fraction: """Compute the product of two cycle indices and evaluate at q. @@ -58,6 +59,7 @@ def cycle_product_for_two_polynomials( Returns: The evaluated result. + """ ans = Fraction(0, 1) for m1 in p1.monomials: @@ -76,6 +78,7 @@ def _cycle_index_sym_helper(n: int, memo: dict[int, Polynomial]) -> Polynomial: Returns: The cycle index polynomial of S_n. + """ if n in memo: return memo[n] @@ -83,7 +86,7 @@ def _cycle_index_sym_helper(n: int, memo: dict[int, Polynomial]) -> Polynomial: for t in range(1, n + 1): ans = ans.__add__( Polynomial([Monomial({t: 1}, Fraction(1, 1))]) - * _cycle_index_sym_helper(n - t, memo) + * _cycle_index_sym_helper(n - t, memo), ) ans *= Fraction(1, n) memo[n] = ans @@ -105,22 +108,24 @@ def get_cycle_index_sym(n: int) -> Polynomial: Examples: >>> get_cycle_index_sym(1) # doctest: +SKIP Polynomial(...) + """ if n < 0: - raise ValueError("n should be a non-negative integer.") + msg = "n should be a non-negative integer." + raise ValueError(msg) memo = { 0: Polynomial([Monomial({}, Fraction(1, 1))]), 1: Polynomial([Monomial({1: 1}, Fraction(1, 1))]), 2: Polynomial( - [Monomial({1: 2}, Fraction(1, 2)), Monomial({2: 1}, Fraction(1, 2))] + [Monomial({1: 2}, Fraction(1, 2)), Monomial({2: 1}, Fraction(1, 2))], ), 3: Polynomial( [ Monomial({1: 3}, Fraction(1, 6)), Monomial({1: 1, 2: 1}, Fraction(1, 2)), Monomial({3: 1}, Fraction(1, 3)), - ] + ], ), 4: Polynomial( [ @@ -129,8 +134,7 @@ def get_cycle_index_sym(n: int) -> Polynomial: Monomial({3: 1, 1: 1}, Fraction(1, 3)), Monomial({2: 2}, Fraction(1, 8)), Monomial({4: 1}, Fraction(1, 4)), - ] + ], ), } - result = _cycle_index_sym_helper(n, memo) - return result + return _cycle_index_sym_helper(n, memo) diff --git a/algorithms/matrix/__init__.py b/algorithms/matrix/__init__.py index e3c55deb6..9a16f4a43 100644 --- a/algorithms/matrix/__init__.py +++ b/algorithms/matrix/__init__.py @@ -1,4 +1,4 @@ -from algorithms.matrix import ( # noqa: F401 +from algorithms.matrix import ( bomb_enemy, cholesky_matrix_decomposition, copy_transform, @@ -13,9 +13,11 @@ sparse_dot_vector, sparse_mul, spiral_traversal, + sudoku_solver, sudoku_validator, sum_sub_squares, ) +from algorithms.matrix.sudoku_solver import Sudoku __all__ = [ "bomb_enemy", @@ -32,6 +34,8 @@ "sparse_dot_vector", "sparse_mul", "spiral_traversal", + "sudoku_solver", "sudoku_validator", "sum_sub_squares", + "Sudoku", ] diff --git a/algorithms/matrix/bomb_enemy.py b/algorithms/matrix/bomb_enemy.py index 3e958822c..c4533e12b 100644 --- a/algorithms/matrix/bomb_enemy.py +++ b/algorithms/matrix/bomb_enemy.py @@ -1,5 +1,4 @@ -""" -Bomb Enemy +"""Bomb Enemy. Given a 2D grid, each cell is either a wall 'W', an enemy 'E' or empty '0' (the number zero). Return the maximum enemies you can kill using one bomb. @@ -29,6 +28,7 @@ def max_killed_enemies(grid: list[list[str]]) -> int: Examples: >>> max_killed_enemies([["0","E","0","0"],["E","0","W","E"],["0","E","0","0"]]) 3 + """ if not grid: return 0 @@ -56,6 +56,7 @@ def _row_kills(grid: list[list[str]], row: int, col: int) -> int: Returns: Number of enemies in the row segment. + """ count = 0 num_cols = len(grid[0]) @@ -76,6 +77,7 @@ def _col_kills(grid: list[list[str]], row: int, col: int) -> int: Returns: Number of enemies in the column segment. + """ count = 0 num_rows = len(grid) diff --git a/algorithms/matrix/cholesky_matrix_decomposition.py b/algorithms/matrix/cholesky_matrix_decomposition.py index 37874a3aa..9bac97b21 100644 --- a/algorithms/matrix/cholesky_matrix_decomposition.py +++ b/algorithms/matrix/cholesky_matrix_decomposition.py @@ -1,5 +1,4 @@ -""" -Cholesky Matrix Decomposition +"""Cholesky Matrix Decomposition. Decompose a Hermitian positive-definite matrix A into a lower-triangular matrix V such that V * V^T = A. Mainly used for numerical solution of @@ -30,6 +29,7 @@ def cholesky_decomposition(matrix: list[list[float]]) -> list[list[float]] | Non Examples: >>> cholesky_decomposition([[4, 12, -16], [12, 37, -43], [-16, -43, 98]]) [[2.0, 0.0, 0.0], [6.0, 1.0, 0.0], [-8.0, 5.0, 3.0]] + """ size = len(matrix) for row in matrix: diff --git a/algorithms/matrix/copy_transform.py b/algorithms/matrix/copy_transform.py index 301f081ca..ad4e57c43 100644 --- a/algorithms/matrix/copy_transform.py +++ b/algorithms/matrix/copy_transform.py @@ -1,5 +1,4 @@ -""" -Copy Transform +"""Copy Transform. Rotate and invert a matrix by creating transformed copies. Provides clockwise rotation, counterclockwise rotation, top-left @@ -27,6 +26,7 @@ def rotate_clockwise(matrix: list[list[int]]) -> list[list[int]]: Examples: >>> rotate_clockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[7, 4, 1], [8, 5, 2], [9, 6, 3]] + """ result: list[list[int]] = [] for row in reversed(matrix): @@ -51,6 +51,7 @@ def rotate_counterclockwise(matrix: list[list[int]]) -> list[list[int]]: Examples: >>> rotate_counterclockwise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[3, 6, 9], [2, 5, 8], [1, 4, 7]] + """ result: list[list[int]] = [] for row in matrix: @@ -75,6 +76,7 @@ def top_left_invert(matrix: list[list[int]]) -> list[list[int]]: Examples: >>> top_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[1, 4, 7], [2, 5, 8], [3, 6, 9]] + """ result: list[list[int]] = [] for row in matrix: @@ -99,6 +101,7 @@ def bottom_left_invert(matrix: list[list[int]]) -> list[list[int]]: Examples: >>> bottom_left_invert([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[9, 6, 3], [8, 5, 2], [7, 4, 1]] + """ result: list[list[int]] = [] for row in reversed(matrix): diff --git a/algorithms/matrix/count_paths.py b/algorithms/matrix/count_paths.py index e61f3a593..3bc7161ed 100644 --- a/algorithms/matrix/count_paths.py +++ b/algorithms/matrix/count_paths.py @@ -1,5 +1,4 @@ -""" -Count Paths +"""Count Paths. Count the number of unique paths from the top-left corner to the bottom-right corner of an m x n grid. Movement is restricted to @@ -28,6 +27,7 @@ def count_paths(rows: int, cols: int) -> int: Examples: >>> count_paths(3, 3) 6 + """ if rows < 1 or cols < 1: return -1 diff --git a/algorithms/matrix/crout_matrix_decomposition.py b/algorithms/matrix/crout_matrix_decomposition.py index b55c05355..6dd081682 100644 --- a/algorithms/matrix/crout_matrix_decomposition.py +++ b/algorithms/matrix/crout_matrix_decomposition.py @@ -1,5 +1,4 @@ -""" -Crout Matrix Decomposition +"""Crout Matrix Decomposition. Decompose a matrix A into lower-triangular matrix L and upper-triangular matrix U such that L * U = A. L has non-zero elements only on and below @@ -30,6 +29,7 @@ def crout_matrix_decomposition( Examples: >>> crout_matrix_decomposition([[9, 9], [7, 7]]) ([[9.0, 0.0], [7.0, 0.0]], [[1.0, 1.0], [0.0, 1.0]]) + """ size = len(matrix) lower = [[0.0] * size for _ in range(size)] diff --git a/algorithms/matrix/matrix_exponentiation.py b/algorithms/matrix/matrix_exponentiation.py index 9abe1069e..ff7283f44 100644 --- a/algorithms/matrix/matrix_exponentiation.py +++ b/algorithms/matrix/matrix_exponentiation.py @@ -1,5 +1,4 @@ -""" -Matrix Exponentiation +"""Matrix Exponentiation. Compute the n-th power of a square matrix using repeated squaring (exponentiation by squaring). Useful for computing Fibonacci numbers, @@ -28,6 +27,7 @@ def multiply(mat_a: list[list[int]], mat_b: list[list[int]]) -> list[list[int]]: Examples: >>> multiply([[1, 0], [0, 1]], [[2, 3], [4, 5]]) [[2, 3], [4, 5]] + """ size = len(mat_a) result = [[0] * size for _ in range(size)] @@ -50,6 +50,7 @@ def identity(size: int) -> list[list[int]]: Examples: >>> identity(3) [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + """ result = [[0] * size for _ in range(size)] for i in range(size): @@ -70,11 +71,11 @@ def matrix_exponentiation(mat: list[list[int]], power: int) -> list[list[int]]: Examples: >>> matrix_exponentiation([[1, 0], [0, 1]], 5) [[1, 0], [0, 1]] + """ if power == 0: return identity(len(mat)) - elif power % 2 == 1: + if power % 2 == 1: return multiply(matrix_exponentiation(mat, power - 1), mat) - else: - half = matrix_exponentiation(mat, power // 2) - return multiply(half, half) + half = matrix_exponentiation(mat, power // 2) + return multiply(half, half) diff --git a/algorithms/matrix/matrix_inversion.py b/algorithms/matrix/matrix_inversion.py index ccfdd9bc7..1a1ade82b 100644 --- a/algorithms/matrix/matrix_inversion.py +++ b/algorithms/matrix/matrix_inversion.py @@ -1,5 +1,4 @@ -""" -Matrix Inversion +"""Matrix Inversion. Compute the inverse of an invertible n x n matrix A, returning an n x n matrix B such that A * B = B * A = I (the identity matrix). Uses cofactor @@ -34,16 +33,17 @@ def invert_matrix( Examples: >>> invert_matrix([[1, 1], [1, 2]]) [[2.0, -1.0], [-1.0, 1.0]] + """ if not _array_is_matrix(matrix): return [[-1]] - elif len(matrix) != len(matrix[0]): + if len(matrix) != len(matrix[0]): return [[-2]] - elif len(matrix) < 2: + if len(matrix) < 2: return [[-3]] - elif get_determinant(matrix) == 0: + if get_determinant(matrix) == 0: return [[-4]] - elif len(matrix) == 2: + if len(matrix) == 2: multiplier = 1 / get_determinant(matrix) inverted = [[multiplier] * len(matrix) for _ in range(len(matrix))] inverted[0][1] = inverted[0][1] * -1 * matrix[0][1] @@ -51,11 +51,9 @@ def invert_matrix( inverted[0][0] = multiplier * matrix[1][1] inverted[1][1] = multiplier * matrix[0][0] return inverted - else: - matrix_of_minors = _get_matrix_of_minors(matrix) - multiplier = fractions.Fraction(1, get_determinant(matrix)) - inverted = _transpose_and_multiply(matrix_of_minors, multiplier) - return inverted + matrix_of_minors = _get_matrix_of_minors(matrix) + multiplier = fractions.Fraction(1, get_determinant(matrix)) + return _transpose_and_multiply(matrix_of_minors, multiplier) def get_determinant(matrix: list[list[int | float]]) -> int | float: @@ -70,6 +68,7 @@ def get_determinant(matrix: list[list[int | float]]) -> int | float: Examples: >>> get_determinant([[1, 2], [3, 4]]) -2 + """ if len(matrix) == 2: return (matrix[0][0] * matrix[1][1]) - (matrix[0][1] * matrix[1][0]) @@ -91,6 +90,7 @@ def _get_matrix_of_minors( Returns: Cofactor matrix. + """ size = len(matrix) result = [[0] * size for _ in range(size)] @@ -102,7 +102,7 @@ def _get_matrix_of_minors( def _get_minor( - matrix: list[list[int | float]], row: int, col: int + matrix: list[list[int | float]], row: int, col: int, ) -> list[list[int | float]]: """Extract the minor by removing the given row and column. @@ -113,6 +113,7 @@ def _get_minor( Returns: Sub-matrix with the specified row and column removed. + """ minors = [] for i in range(len(matrix)): @@ -125,7 +126,7 @@ def _get_minor( def _transpose_and_multiply( matrix: list[list[int | float]], - multiplier: int | float | fractions.Fraction = 1, + multiplier: float | fractions.Fraction = 1, ) -> list[list[int | float | fractions.Fraction]]: """Transpose the matrix and multiply each element by a scalar. @@ -135,6 +136,7 @@ def _transpose_and_multiply( Returns: Transposed and scaled matrix. + """ for row in range(len(matrix)): for col in range(row + 1): @@ -152,6 +154,7 @@ def _array_is_matrix(matrix: list[list]) -> bool: Returns: True if all rows have the same length, False otherwise. + """ if len(matrix) == 0: return False diff --git a/algorithms/matrix/multiply.py b/algorithms/matrix/multiply.py index dc4814ffb..1673640a2 100644 --- a/algorithms/matrix/multiply.py +++ b/algorithms/matrix/multiply.py @@ -1,5 +1,4 @@ -""" -Matrix Multiplication +"""Matrix Multiplication. Multiply two compatible matrices and return their product. The number of columns in the multiplicand must equal the number of rows in the multiplier. @@ -15,7 +14,7 @@ def multiply( - multiplicand: list[list[int]], multiplier: list[list[int]] + multiplicand: list[list[int]], multiplier: list[list[int]], ) -> list[list[int]]: """Multiply two matrices. @@ -32,11 +31,13 @@ def multiply( Examples: >>> multiply([[1, 2, 3], [2, 1, 1]], [[1], [2], [3]]) [[14], [7]] + """ multiplicand_rows, multiplicand_cols = len(multiplicand), len(multiplicand[0]) multiplier_rows, multiplier_cols = len(multiplier), len(multiplier[0]) if multiplicand_cols != multiplier_rows: - raise Exception("Multiplicand matrix not compatible with Multiplier matrix.") + msg = "Multiplicand matrix not compatible with Multiplier matrix." + raise Exception(msg) result = [[0] * multiplier_cols for _ in range(multiplicand_rows)] for i in range(multiplicand_rows): for j in range(multiplier_cols): diff --git a/algorithms/matrix/rotate_image.py b/algorithms/matrix/rotate_image.py index e2f7f05c1..b5b129a8c 100644 --- a/algorithms/matrix/rotate_image.py +++ b/algorithms/matrix/rotate_image.py @@ -1,5 +1,4 @@ -""" -Rotate Image +"""Rotate Image. Rotate an n x n 2D matrix representing an image by 90 degrees clockwise, in-place. First reverse the rows top-to-bottom, then transpose. @@ -26,6 +25,7 @@ def rotate(mat: list[list[int]]) -> list[list[int]]: Examples: >>> rotate([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [[7, 4, 1], [8, 5, 2], [9, 6, 3]] + """ if not mat: return mat diff --git a/algorithms/matrix/search_in_sorted_matrix.py b/algorithms/matrix/search_in_sorted_matrix.py index 77a3ac6f6..c5934053d 100644 --- a/algorithms/matrix/search_in_sorted_matrix.py +++ b/algorithms/matrix/search_in_sorted_matrix.py @@ -1,5 +1,4 @@ -""" -Search in Sorted Matrix +"""Search in Sorted Matrix. Search for a key in a matrix that is sorted row-wise and column-wise in non-decreasing order. Start from the bottom-left corner and move @@ -16,7 +15,7 @@ def search_in_a_sorted_matrix( - mat: list[list[int]], rows: int, cols: int, key: int + mat: list[list[int]], rows: int, cols: int, key: int, ) -> bool: """Search for a key in a row-wise and column-wise sorted matrix. @@ -34,6 +33,7 @@ def search_in_a_sorted_matrix( True >>> search_in_a_sorted_matrix([[2, 5, 7], [4, 8, 13], [9, 11, 15]], 3, 3, 6) False + """ i, j = rows - 1, 0 while i >= 0 and j < cols: diff --git a/algorithms/matrix/sort_matrix_diagonally.py b/algorithms/matrix/sort_matrix_diagonally.py index 2a4a86d79..bd481e112 100644 --- a/algorithms/matrix/sort_matrix_diagonally.py +++ b/algorithms/matrix/sort_matrix_diagonally.py @@ -1,5 +1,4 @@ -""" -Sort Matrix Diagonally +"""Sort Matrix Diagonally. Given an m x n matrix of integers, sort each diagonal from top-left to bottom-right in ascending order and return the sorted matrix. Uses a @@ -29,6 +28,7 @@ def sort_diagonally(mat: list[list[int]]) -> list[list[int]]: Examples: >>> sort_diagonally([[3, 3, 1, 1], [2, 2, 1, 2], [1, 1, 1, 2]]) [[1, 1, 1, 1], [1, 2, 2, 2], [1, 2, 3, 3]] + """ if len(mat) == 1 or len(mat[0]) == 1: return mat diff --git a/algorithms/matrix/sparse_dot_vector.py b/algorithms/matrix/sparse_dot_vector.py index a95c771db..4d8913b03 100644 --- a/algorithms/matrix/sparse_dot_vector.py +++ b/algorithms/matrix/sparse_dot_vector.py @@ -1,5 +1,4 @@ -""" -Sparse Dot Vector +"""Sparse Dot Vector. Compute the dot product of two large sparse vectors efficiently by converting them to index-value pair representations and merging. @@ -28,6 +27,7 @@ def vector_to_index_value_list( Examples: >>> vector_to_index_value_list([0.0, 2.0, 0.0, 3.0]) [(1, 2.0), (3, 3.0)] + """ return [(i, v) for i, v in enumerate(vector) if v != 0.0] @@ -48,6 +48,7 @@ def dot_product( Examples: >>> dot_product([(0, 1.0), (1, 2.0), (2, 3.0)], [(1, 2.0), (2, 2.0)]) 10.0 + """ product = 0 p1 = len(iv_list1) - 1 diff --git a/algorithms/matrix/sparse_mul.py b/algorithms/matrix/sparse_mul.py index 062812301..67c0e35a7 100644 --- a/algorithms/matrix/sparse_mul.py +++ b/algorithms/matrix/sparse_mul.py @@ -1,5 +1,4 @@ -""" -Sparse Matrix Multiplication +"""Sparse Matrix Multiplication. Given two sparse matrices A and B, return their product A * B. Skips zero elements for efficiency. A's column count must equal @@ -16,7 +15,7 @@ def sparse_multiply( - mat_a: list[list[int]], mat_b: list[list[int]] + mat_a: list[list[int]], mat_b: list[list[int]], ) -> list[list[int]] | None: """Multiply two sparse matrices, skipping zero elements. @@ -33,13 +32,15 @@ def sparse_multiply( Examples: >>> sparse_multiply([[1, 0, 0], [-1, 0, 3]], [[7, 0, 0], [0, 0, 0], [0, 0, 1]]) [[7, 0, 0], [-7, 0, 3]] + """ if mat_a is None or mat_b is None: return None rows_a, cols_a = len(mat_a), len(mat_a[0]) cols_b = len(mat_b[0]) if len(mat_b) != cols_a: - raise Exception("A's column number must be equal to B's row number.") + msg = "A's column number must be equal to B's row number." + raise Exception(msg) result = [[0] * cols_b for _ in range(rows_a)] for i, row in enumerate(mat_a): for k, elem_a in enumerate(row): diff --git a/algorithms/matrix/spiral_traversal.py b/algorithms/matrix/spiral_traversal.py index f696408dc..761988017 100644 --- a/algorithms/matrix/spiral_traversal.py +++ b/algorithms/matrix/spiral_traversal.py @@ -1,5 +1,4 @@ -""" -Spiral Traversal +"""Spiral Traversal. Return all elements of an m x n matrix in spiral order, traversing right, down, left, and up repeatedly while shrinking the boundaries. @@ -26,6 +25,7 @@ def spiral_traversal(matrix: list[list[int]]) -> list[int]: Examples: >>> spiral_traversal([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) [1, 2, 3, 6, 9, 8, 7, 4, 5] + """ result: list[int] = [] if len(matrix) == 0: diff --git a/algorithms/matrix/sudoku_solver.py b/algorithms/matrix/sudoku_solver.py new file mode 100644 index 000000000..5534174c1 --- /dev/null +++ b/algorithms/matrix/sudoku_solver.py @@ -0,0 +1,147 @@ +"""Sudoku Solver (DFS / Backtracking). + +Solves a Sudoku puzzle using constraint propagation and depth-first search +with backtracking, starting from the cell with the fewest possible values. + +Reference: https://leetcode.com/problems/sudoku-solver/ + +Complexity: + Time: O(9^(empty cells)) worst case + Space: O(N^2) +""" + +from __future__ import annotations + + +class Sudoku: + """A Sudoku board solver.""" + + def __init__( + self, + board: list[list[str]], + row: int, + col: int, + ) -> None: + """Initialise the solver with the given board. + + Args: + board: 2D list of digits or '.' for empty cells. + row: Number of rows. + col: Number of columns. + + """ + self.board = board + self.row = row + self.col = col + self.val = self._possible_values() + + def _possible_values(self) -> dict[tuple[int, int], list[str]]: + """Compute possible values for each empty cell. + + Returns: + Mapping from (row, col) to list of candidate digits. + + """ + a = "123456789" + d: dict[tuple[str, int] | tuple[int, int], list[str]] = {} + val: dict[tuple[int, int], list[str]] = {} + for i in range(self.row): + for j in range(self.col): + ele = self.board[i][j] + if ele != ".": + d[("r", i)] = [*d.get(("r", i), []), ele] + d[("c", j)] = [*d.get(("c", j), []), ele] + d[(i // 3, j // 3)] = [*d.get((i // 3, j // 3), []), ele] + else: + val[(i, j)] = [] + for i, j in val: + inval = ( + d.get(("r", i), []) + + d.get(("c", j), []) + + d.get((i // 3, j // 3), []) + ) + val[(i, j)] = [n for n in a if n not in inval] + return val + + def solve(self) -> bool: + """Attempt to solve the board in place. + + Returns: + True if a solution was found. + + """ + if len(self.val) == 0: + return True + kee = min(self.val.keys(), key=lambda x: len(self.val[x])) + nums = self.val[kee] + for n in nums: + update: dict[tuple[int, int], str | list[str]] = {kee: self.val[kee]} + if self._valid_one(n, kee, update) and self.solve(): + return True + self._undo(kee, update) + return False + + def _valid_one( + self, + n: str, + kee: tuple[int, int], + update: dict[tuple[int, int], str | list[str]], + ) -> bool: + """Place digit *n* at *kee* and propagate constraints. + + Args: + n: Digit to place. + kee: (row, col) coordinate. + update: Undo log (modified in place). + + Returns: + True if placement is valid. + + """ + self.board[kee[0]][kee[1]] = n + del self.val[kee] + i, j = kee + for ind in list(self.val.keys()): + if n in self.val[ind] and ( + ind[0] == i + or ind[1] == j + or (ind[0] // 3, ind[1] // 3) == (i // 3, j // 3) + ): + update[ind] = n + self.val[ind].remove(n) + if len(self.val[ind]) == 0: + return False + return True + + def _undo( + self, + kee: tuple[int, int], + update: dict[tuple[int, int], str | list[str]], + ) -> None: + """Revert the placement at *kee* using *update*. + + Args: + kee: (row, col) coordinate. + update: Undo log. + + """ + self.board[kee[0]][kee[1]] = "." + for k, v in update.items(): + if k not in self.val: + self.val[k] = v + else: + self.val[k].append(v) + + def __str__(self) -> str: + """Return a string representation of the board. + + Returns: + Formatted board string. + + """ + resp = "" + for i in range(self.row): + for j in range(self.col): + resp += f" {self.board[i][j]} " + resp += "\n" + return resp diff --git a/algorithms/matrix/sudoku_validator.py b/algorithms/matrix/sudoku_validator.py index e2e2abe45..830abe713 100644 --- a/algorithms/matrix/sudoku_validator.py +++ b/algorithms/matrix/sudoku_validator.py @@ -1,5 +1,4 @@ -""" -Sudoku Validator +"""Sudoku Validator. Validate whether a completed 9x9 Sudoku board is a valid solution. Each row, column, and 3x3 sub-box must contain digits 1-9 without @@ -14,6 +13,8 @@ from __future__ import annotations +import functools +import operator from collections import defaultdict @@ -34,6 +35,7 @@ def valid_solution_hashtable(board: list[list[int]]) -> bool: ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution_hashtable(board) True + """ for i in range(len(board)): row_seen = defaultdict(int) @@ -45,12 +47,10 @@ def valid_solution_hashtable(board: list[list[int]]) -> bool: return False if value_row in row_seen: return False - else: - row_seen[value_row] += 1 + row_seen[value_row] += 1 if value_col in col_seen: return False - else: - col_seen[value_col] += 1 + col_seen[value_col] += 1 for i in range(3): for j in range(3): @@ -80,6 +80,7 @@ def valid_solution(board: list[list[int]]) -> bool: ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution(board) True + """ correct = [1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -119,6 +120,7 @@ def valid_solution_set(board: list[list[int]]) -> bool: ... [3,4,5,2,8,6,1,7,9]] >>> valid_solution_set(board) True + """ valid = set(range(1, 10)) @@ -134,13 +136,10 @@ def valid_solution_set(board: list[list[int]]) -> bool: for y in range(3): if ( set( - sum( - [ + functools.reduce(operator.iadd, [ row[x * 3 : (x + 1) * 3] for row in board[y * 3 : (y + 1) * 3] - ], - [], - ) + ], []), ) != valid ): diff --git a/algorithms/matrix/sum_sub_squares.py b/algorithms/matrix/sum_sub_squares.py index 0f8ccd02f..05d4e062d 100644 --- a/algorithms/matrix/sum_sub_squares.py +++ b/algorithms/matrix/sum_sub_squares.py @@ -1,5 +1,4 @@ -""" -Sum of Sub-Squares +"""Sum of Sub-Squares. Given a square matrix of size n x n and an integer k, compute the sum of all k x k sub-squares and return the results as a matrix. @@ -27,6 +26,7 @@ def sum_sub_squares(matrix: list[list[int]], k: int) -> list[list[int]] | None: Examples: >>> sum_sub_squares([[1, 1, 1], [2, 2, 2], [3, 3, 3]], 2) [[6, 6], [10, 10]] + """ size = len(matrix) if k > size: diff --git a/algorithms/queue/max_sliding_window.py b/algorithms/queue/max_sliding_window.py index 9831e3f98..f11d7f660 100644 --- a/algorithms/queue/max_sliding_window.py +++ b/algorithms/queue/max_sliding_window.py @@ -1,5 +1,4 @@ -""" -Max Sliding Window (Deque-based) +"""Max Sliding Window (Deque-based). Given an array and a window size k, find the maximum element in each sliding window using a monotonic deque that stores indices of elements @@ -34,6 +33,7 @@ def max_sliding_window(arr: list[int], k: int) -> list[int]: Examples: >>> max_sliding_window([1, 3, -1, -3, 5, 3, 6, 7], 3) [3, 3, 5, 5, 6, 7] + """ index_deque: collections.deque[int] = collections.deque() result: list[int] = [] diff --git a/algorithms/queue/moving_average.py b/algorithms/queue/moving_average.py index e298e96e0..2de8ec3f3 100644 --- a/algorithms/queue/moving_average.py +++ b/algorithms/queue/moving_average.py @@ -1,5 +1,4 @@ -""" -Moving Average from Data Stream +"""Moving Average from Data Stream. Calculate the moving average of integers in a sliding window of fixed size using a bounded deque. @@ -25,6 +24,7 @@ class MovingAverage: 1.0 >>> m.next(10) 5.5 + """ def __init__(self, size: int) -> None: @@ -32,6 +32,7 @@ def __init__(self, size: int) -> None: Args: size: The window size for the moving average. + """ self.queue: deque[int] = deque(maxlen=size) @@ -43,6 +44,7 @@ def next(self, val: int) -> float: Returns: The current moving average as a float. + """ self.queue.append(val) return sum(self.queue) / len(self.queue) diff --git a/algorithms/queue/reconstruct_queue.py b/algorithms/queue/reconstruct_queue.py index 2f2119b28..1328253e7 100644 --- a/algorithms/queue/reconstruct_queue.py +++ b/algorithms/queue/reconstruct_queue.py @@ -1,5 +1,4 @@ -""" -Reconstruct Queue by Height +"""Reconstruct Queue by Height. Given a list of people described by (height, k) pairs where k is the number of taller-or-equal people in front, reconstruct the queue by @@ -27,6 +26,7 @@ def reconstruct_queue(people: list[list[int]]) -> list[list[int]]: Examples: >>> reconstruct_queue([[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]) [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]] + """ queue: list[list[int]] = [] people.sort(key=lambda x: (-x[0], x[1])) diff --git a/algorithms/queue/zigzagiterator.py b/algorithms/queue/zigzagiterator.py index 55d9d51da..a81d93afb 100644 --- a/algorithms/queue/zigzagiterator.py +++ b/algorithms/queue/zigzagiterator.py @@ -1,5 +1,4 @@ -""" -Zigzag Iterator +"""Zigzag Iterator. Interleave elements from two lists in a zigzag fashion. Elements are yielded alternately from each list until both are exhausted. @@ -25,6 +24,7 @@ class ZigZagIterator: 1 >>> it.next() 3 + """ def __init__(self, v1: list[int], v2: list[int]) -> None: @@ -33,6 +33,7 @@ def __init__(self, v1: list[int], v2: list[int]) -> None: Args: v1: First input list. v2: Second input list. + """ self.queue: deque[list[int]] = deque(lst for lst in (v1, v2) if lst) @@ -41,6 +42,7 @@ def next(self) -> int: Returns: The next interleaved element. + """ current_list = self.queue.popleft() ret = current_list.pop(0) @@ -53,5 +55,6 @@ def has_next(self) -> bool: Returns: True if elements remain, False otherwise. + """ return bool(self.queue) diff --git a/algorithms/searching/__init__.py b/algorithms/searching/__init__.py index 478ab9d51..f2f643a37 100644 --- a/algorithms/searching/__init__.py +++ b/algorithms/searching/__init__.py @@ -25,6 +25,7 @@ "binary_search", "binary_search_first_true", "binary_search_recur", + "exponential_search", "find_min_rotate", "find_min_rotate_recur", "first_occurrence", @@ -39,10 +40,9 @@ "search_range", "search_rotate", "search_rotate_recur", + "sentinel_search", "ternary_search", "two_sum", "two_sum1", "two_sum2", - "exponential_search", - "sentinel_search", ] diff --git a/algorithms/searching/binary_search.py b/algorithms/searching/binary_search.py index 3e27a7a93..2ea1c2628 100644 --- a/algorithms/searching/binary_search.py +++ b/algorithms/searching/binary_search.py @@ -1,5 +1,4 @@ -""" -Binary Search +"""Binary Search. Search for an element in a sorted array by repeatedly dividing the search interval in half. @@ -29,6 +28,7 @@ def binary_search(array: list[int], query: int) -> int: 2 >>> binary_search([1, 2, 3, 4, 5], 6) -1 + """ low, high = 0, len(array) - 1 while low <= high: @@ -60,6 +60,7 @@ def binary_search_recur(array: list[int], low: int, high: int, val: int) -> int: 2 >>> binary_search_recur([1, 2, 3, 4, 5], 0, 4, 6) -1 + """ if low > high: return -1 diff --git a/algorithms/searching/exponential_search.py b/algorithms/searching/exponential_search.py index f71d9deaf..1a5294b78 100644 --- a/algorithms/searching/exponential_search.py +++ b/algorithms/searching/exponential_search.py @@ -27,7 +27,7 @@ def exponential_search(arr: list[int], target: int) -> int: mid = (low + high) // 2 if arr[mid] == target: return mid - elif arr[mid] < target: + if arr[mid] < target: low = mid + 1 else: high = mid - 1 diff --git a/algorithms/searching/find_min_rotate.py b/algorithms/searching/find_min_rotate.py index 4ea2028e4..f24e60c4b 100644 --- a/algorithms/searching/find_min_rotate.py +++ b/algorithms/searching/find_min_rotate.py @@ -1,5 +1,4 @@ -""" -Find Minimum in Rotated Sorted Array +"""Find Minimum in Rotated Sorted Array. Find the minimum element in a sorted array that has been rotated at some unknown pivot. Assumes no duplicates exist in the array. @@ -28,6 +27,7 @@ def find_min_rotate(array: list[int]) -> int: 0 >>> find_min_rotate([1, 2, 3]) 1 + """ low = 0 high = len(array) - 1 @@ -54,6 +54,7 @@ def find_min_rotate_recur(array: list[int], low: int, high: int) -> int: Examples: >>> find_min_rotate_recur([4, 5, 6, 7, 0, 1, 2], 0, 6) 0 + """ mid = (low + high) // 2 if mid == low: diff --git a/algorithms/searching/first_occurrence.py b/algorithms/searching/first_occurrence.py index cafec2804..2810d6661 100644 --- a/algorithms/searching/first_occurrence.py +++ b/algorithms/searching/first_occurrence.py @@ -1,5 +1,4 @@ -""" -First Occurrence +"""First Occurrence. Find the index of the first occurrence of a target value in a sorted array using binary search. @@ -29,6 +28,7 @@ def first_occurrence(array: list[int], query: int) -> int: 1 >>> first_occurrence([1, 2, 3, 4, 5], 6) -1 + """ low, high = 0, len(array) - 1 while low <= high: diff --git a/algorithms/searching/generalized_binary_search.py b/algorithms/searching/generalized_binary_search.py index 563b536ae..420f56aa1 100644 --- a/algorithms/searching/generalized_binary_search.py +++ b/algorithms/searching/generalized_binary_search.py @@ -1,5 +1,4 @@ -""" -Generalized Binary Search +"""Generalized Binary Search. Find the smallest value in a numeric range for which a monotonic boolean predicate evaluates to True. Instead of searching for a specific value in @@ -15,7 +14,10 @@ from __future__ import annotations -from collections.abc import Callable +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable def binary_search_first_true( @@ -44,6 +46,7 @@ def binary_search_first_true( 5 >>> binary_search_first_true(0, 5, lambda x: x > 10) -1 + """ result = -1 @@ -57,9 +60,3 @@ def binary_search_first_true( low = mid + 1 return result - - -if __name__ == "__main__": - print(binary_search_first_true(0, 10, lambda x: x >= 7)) # 7 - print(binary_search_first_true(0, 10, lambda x: x * x >= 25)) # 5 - print(binary_search_first_true(0, 5, lambda x: x > 10)) # -1 diff --git a/algorithms/searching/interpolation_search.py b/algorithms/searching/interpolation_search.py index 42207eb0a..8d953f337 100644 --- a/algorithms/searching/interpolation_search.py +++ b/algorithms/searching/interpolation_search.py @@ -1,5 +1,4 @@ -""" -Interpolation Search +"""Interpolation Search. Search for a target value in a uniformly distributed sorted array by estimating the position of the target using linear interpolation. @@ -31,13 +30,14 @@ def interpolation_search(array: list[int], search_key: int) -> int: -1 >>> interpolation_search([5, 10, 12, 14, 17, 20, 21], -5) -1 + """ high = len(array) - 1 low = 0 while (low <= high) and (array[low] <= search_key <= array[high]): pos = low + int( - ((search_key - array[low]) * (high - low)) / (array[high] - array[low]) + ((search_key - array[low]) * (high - low)) / (array[high] - array[low]), ) if array[pos] == search_key: @@ -49,9 +49,3 @@ def interpolation_search(array: list[int], search_key: int) -> int: high = pos - 1 return -1 - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/algorithms/searching/jump_search.py b/algorithms/searching/jump_search.py index 7964cf6c8..c1a296cfc 100644 --- a/algorithms/searching/jump_search.py +++ b/algorithms/searching/jump_search.py @@ -1,5 +1,4 @@ -""" -Jump Search +"""Jump Search. Search for a target value in a sorted array by jumping ahead in fixed-size blocks and then performing a linear search within the identified block. @@ -31,6 +30,7 @@ def jump_search(array: list[int], target: int) -> int: 4 >>> jump_search([1, 2, 3, 4, 5], 0) -1 + """ length = len(array) block_size = int(math.sqrt(length)) diff --git a/algorithms/searching/last_occurrence.py b/algorithms/searching/last_occurrence.py index ae7f5e7e9..c7b963a13 100644 --- a/algorithms/searching/last_occurrence.py +++ b/algorithms/searching/last_occurrence.py @@ -1,5 +1,4 @@ -""" -Last Occurrence +"""Last Occurrence. Find the index of the last occurrence of a target value in a sorted array using binary search. @@ -29,6 +28,7 @@ def last_occurrence(array: list[int], query: int) -> int: 3 >>> last_occurrence([1, 2, 3, 4, 5], 6) -1 + """ low, high = 0, len(array) - 1 while low <= high: diff --git a/algorithms/searching/linear_search.py b/algorithms/searching/linear_search.py index 47a1eb707..df7c0a1c3 100644 --- a/algorithms/searching/linear_search.py +++ b/algorithms/searching/linear_search.py @@ -1,5 +1,4 @@ -""" -Linear Search +"""Linear Search. Search for a target value in an array by checking every element sequentially. The array does not need to be sorted. @@ -29,6 +28,7 @@ def linear_search(array: list[int], query: int) -> int: 2 >>> linear_search([5, 1, 3, 2, 4], 6) -1 + """ for i, value in enumerate(array): if value == query: diff --git a/algorithms/searching/next_greatest_letter.py b/algorithms/searching/next_greatest_letter.py index e3b8a2a8f..e0016aed5 100644 --- a/algorithms/searching/next_greatest_letter.py +++ b/algorithms/searching/next_greatest_letter.py @@ -1,5 +1,4 @@ -""" -Next Greatest Letter +"""Next Greatest Letter. Given a sorted list of lowercase letters and a target letter, find the smallest letter in the list that is larger than the target. Letters wrap @@ -35,6 +34,7 @@ def next_greatest_letter(letters: list[str], target: str) -> str: 'c' >>> next_greatest_letter(["c", "f", "j"], "c") 'f' + """ index = bisect.bisect(letters, target) return letters[index % len(letters)] @@ -54,6 +54,7 @@ def next_greatest_letter_v1(letters: list[str], target: str) -> str: Examples: >>> next_greatest_letter_v1(["c", "f", "j"], "d") 'f' + """ if letters[0] > target: return letters[0] @@ -83,6 +84,7 @@ def next_greatest_letter_v2(letters: list[str], target: str) -> str: Examples: >>> next_greatest_letter_v2(["c", "f", "j"], "d") 'f' + """ for letter in letters: if letter > target: diff --git a/algorithms/searching/search_insert.py b/algorithms/searching/search_insert.py index 69b233894..2c33c5082 100644 --- a/algorithms/searching/search_insert.py +++ b/algorithms/searching/search_insert.py @@ -1,5 +1,4 @@ -""" -Search Insert Position +"""Search Insert Position. Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in @@ -35,6 +34,7 @@ def search_insert(array: list[int], val: int) -> int: 4 >>> search_insert([1, 3, 5, 6], 0) 0 + """ low = 0 high = len(array) - 1 diff --git a/algorithms/searching/search_range.py b/algorithms/searching/search_range.py index acce21fb0..04a3ed4cc 100644 --- a/algorithms/searching/search_range.py +++ b/algorithms/searching/search_range.py @@ -1,5 +1,4 @@ -""" -Search Range +"""Search Range. Given a sorted array of integers and a target value, find the starting and ending positions of the target. Returns [-1, -1] if the target is not found. @@ -31,6 +30,7 @@ def search_range(nums: list[int], target: int) -> list[int]: [3, 5] >>> search_range([5, 7, 7, 8, 8, 8, 10], 11) [-1, -1] + """ low = 0 high = len(nums) - 1 diff --git a/algorithms/searching/search_rotate.py b/algorithms/searching/search_rotate.py index e9dbb871c..b3650e8e3 100644 --- a/algorithms/searching/search_rotate.py +++ b/algorithms/searching/search_rotate.py @@ -1,5 +1,4 @@ -""" -Search in Rotated Sorted Array +"""Search in Rotated Sorted Array. Search for a target value in an array that was sorted in ascending order and then rotated at some unknown pivot. One half of the array is always in sorted @@ -31,6 +30,7 @@ def search_rotate(array: list[int], val: int) -> int: 4 >>> search_rotate([4, 5, 6, 7, 0, 1, 2], 3) -1 + """ low, high = 0, len(array) - 1 while low <= high: @@ -43,11 +43,10 @@ def search_rotate(array: list[int], val: int) -> int: high = mid - 1 else: low = mid + 1 + elif array[mid] <= val <= array[high]: + low = mid + 1 else: - if array[mid] <= val <= array[high]: - low = mid + 1 - else: - high = mid - 1 + high = mid - 1 return -1 @@ -73,6 +72,7 @@ def search_rotate_recur( Examples: >>> search_rotate_recur([4, 5, 6, 7, 0, 1, 2], 0, 6, 0) 4 + """ if low > high: return -1 diff --git a/algorithms/searching/ternary_search.py b/algorithms/searching/ternary_search.py index 80a1df810..d0f2763d6 100644 --- a/algorithms/searching/ternary_search.py +++ b/algorithms/searching/ternary_search.py @@ -1,5 +1,4 @@ -""" -Ternary Search +"""Ternary Search. Search for a target value in a sorted array by dividing the search range into three equal parts instead of two. At each step two midpoints are computed and @@ -32,6 +31,7 @@ def ternary_search(left: int, right: int, key: int, array: list[int]) -> int: 4 >>> ternary_search(0, 4, 0, [1, 2, 3, 4, 5]) -1 + """ while right >= left: mid1 = left + (right - left) // 3 diff --git a/algorithms/searching/two_sum.py b/algorithms/searching/two_sum.py index f689305e1..8808de3ce 100644 --- a/algorithms/searching/two_sum.py +++ b/algorithms/searching/two_sum.py @@ -1,5 +1,4 @@ -""" -Two Sum +"""Two Sum. Given a sorted array of integers and a target sum, find the 1-based indices of the two numbers that add up to the target. Three approaches are provided: @@ -33,6 +32,7 @@ def two_sum(numbers: list[int], target: int) -> list[int] | None: [1, 2] >>> two_sum([1, 2, 3], 7) is None True + """ for i, number in enumerate(numbers): second_val = target - number @@ -63,6 +63,7 @@ def two_sum1(numbers: list[int], target: int) -> list[int] | None: Examples: >>> two_sum1([2, 7, 11, 15], 9) [1, 2] + """ seen: dict[int, int] = {} for i, num in enumerate(numbers): @@ -87,6 +88,7 @@ def two_sum2(numbers: list[int], target: int) -> list[int] | None: Examples: >>> two_sum2([2, 7, 11, 15], 9) [1, 2] + """ left = 0 right = len(numbers) - 1 diff --git a/algorithms/set/find_keyboard_row.py b/algorithms/set/find_keyboard_row.py index f25853d86..bd1b038e1 100644 --- a/algorithms/set/find_keyboard_row.py +++ b/algorithms/set/find_keyboard_row.py @@ -1,5 +1,4 @@ -""" -Keyboard Row Filter +"""Keyboard Row Filter. Given a list of words, return the words that can be typed using letters from only one row of an American QWERTY keyboard. @@ -32,6 +31,7 @@ def find_keyboard_row(words: list[str]) -> list[str]: Examples: >>> find_keyboard_row(["Hello", "Alaska", "Dad", "Peace"]) ['Alaska', 'Dad'] + """ result: list[str] = [] for word in words: diff --git a/algorithms/set/randomized_set.py b/algorithms/set/randomized_set.py index c99bafdfe..b57fa29d0 100644 --- a/algorithms/set/randomized_set.py +++ b/algorithms/set/randomized_set.py @@ -1,59 +1,6 @@ -""" -Randomized Set - -A data structure that supports insert, remove, and get-random-element -operations, all in average O(1) time. +"""Randomized Set (re-exported from algorithms.map.randomized_set). -Reference: https://leetcode.com/problems/insert-delete-getrandom-o1/ - -Complexity: - Time: O(1) average for insert, remove, and random_element - Space: O(n) +The canonical implementation lives in algorithms/map/randomized_set.py. """ -from __future__ import annotations - -import random - - -class RandomizedSet: - """A set supporting O(1) insert, remove, and random access.""" - - def __init__(self) -> None: - self.elements: list[int] = [] - self.index_map: dict[int, int] = {} - - def insert(self, new_one: int) -> None: - """Insert a value into the set if not already present. - - Args: - new_one: The value to insert. - """ - if new_one in self.index_map: - return - self.index_map[new_one] = len(self.elements) - self.elements.append(new_one) - - def remove(self, old_one: int) -> None: - """Remove a value from the set if present. - - Args: - old_one: The value to remove. - """ - if old_one not in self.index_map: - return - index = self.index_map[old_one] - last = self.elements.pop() - self.index_map.pop(old_one) - if index == len(self.elements): - return - self.elements[index] = last - self.index_map[last] = index - - def random_element(self) -> int: - """Return a random element from the set. - - Returns: - A randomly chosen element. - """ - return random.choice(self.elements) +from algorithms.map.randomized_set import RandomizedSet # noqa: F401 diff --git a/algorithms/set/set_covering.py b/algorithms/set/set_covering.py index 2fcc47108..0ff67c47f 100644 --- a/algorithms/set/set_covering.py +++ b/algorithms/set/set_covering.py @@ -1,5 +1,4 @@ -""" -Set Cover Problem +"""Set Cover Problem. Given a universe U of n elements, a collection S of subsets of U, and a cost for each subset, find the minimum-cost sub-collection that covers all of U. @@ -28,6 +27,7 @@ def _powerset(iterable: list[str]) -> chain[tuple[str, ...]]: Returns: An iterator over all subsets (the power set). + """ items = list(iterable) return chain.from_iterable(combinations(items, r) for r in range(len(items) + 1)) @@ -57,6 +57,7 @@ def optimal_set_cover( >>> costs = {'S1': 5, 'S2': 10, 'S3': 3} >>> optimal_set_cover(universe, subsets, costs) ('S2', 'S3') + """ pset = _powerset(list(subsets.keys())) best_set: tuple[str, ...] | None = None @@ -95,8 +96,9 @@ def greedy_set_cover( >>> costs = {'S1': 5, 'S2': 10, 'S3': 3} >>> greedy_set_cover(universe, subsets, costs) ['S3', 'S2'] + """ - all_elements = set(e for s in subsets.values() for e in s) + all_elements = {e for s in subsets.values() for e in s} if all_elements != universe: return None diff --git a/algorithms/sorting/bead_sort.py b/algorithms/sorting/bead_sort.py index 557f16fe4..8eb8bc1cc 100644 --- a/algorithms/sorting/bead_sort.py +++ b/algorithms/sorting/bead_sort.py @@ -1,5 +1,4 @@ -""" -Bead Sort +"""Bead Sort. Bead sort (also known as gravity sort) simulates how beads settle under gravity on an abacus. It only works with non-negative integers. @@ -29,9 +28,11 @@ def bead_sort(array: list[int]) -> list[int]: Examples: >>> bead_sort([6, 3, 4, 1, 5, 2]) [1, 2, 3, 4, 5, 6] + """ if any(num < 0 for num in array): - raise ValueError("Bead sort only works with non-negative integers.") + msg = "Bead sort only works with non-negative integers." + raise ValueError(msg) max_value = max(array) if array else 0 grid = [[0] * len(array) for _ in range(max_value)] diff --git a/algorithms/sorting/bitonic_sort.py b/algorithms/sorting/bitonic_sort.py index 43d344450..bff3ac6c9 100644 --- a/algorithms/sorting/bitonic_sort.py +++ b/algorithms/sorting/bitonic_sort.py @@ -1,5 +1,4 @@ -""" -Bitonic Sort +"""Bitonic Sort. Bitonic sort is a comparison-based sorting algorithm designed for parallel execution. This implementation is sequential. The input size must be a @@ -31,12 +30,14 @@ def bitonic_sort(array: list[int], reverse: bool = False) -> list[int]: Examples: >>> bitonic_sort([4, 2, 1, 3]) [1, 2, 3, 4] + """ n = len(array) if n <= 1: return array if not (n and not (n & (n - 1))): - raise ValueError("the size of input should be power of two") + msg = "the size of input should be power of two" + raise ValueError(msg) left = bitonic_sort(array[: n // 2], True) right = bitonic_sort(array[n // 2 :], False) diff --git a/algorithms/sorting/bogo_sort.py b/algorithms/sorting/bogo_sort.py index 9bde7c232..beec489eb 100644 --- a/algorithms/sorting/bogo_sort.py +++ b/algorithms/sorting/bogo_sort.py @@ -1,5 +1,4 @@ -""" -Bogo Sort +"""Bogo Sort. Bogo sort repeatedly shuffles the array at random until it happens to be sorted. It is extremely inefficient and used only for educational purposes. @@ -28,6 +27,7 @@ def bogo_sort(array: list[int]) -> list[int]: Examples: >>> bogo_sort([3, 1, 2]) [1, 2, 3] + """ while not _is_sorted(array): random.shuffle(array) diff --git a/algorithms/sorting/bubble_sort.py b/algorithms/sorting/bubble_sort.py index 364ba3e62..ee44f2ab8 100644 --- a/algorithms/sorting/bubble_sort.py +++ b/algorithms/sorting/bubble_sort.py @@ -1,5 +1,4 @@ -""" -Bubble Sort +"""Bubble Sort. Bubble sort repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. @@ -26,6 +25,7 @@ def bubble_sort(array: list[int]) -> list[int]: Examples: >>> bubble_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) swapped = True diff --git a/algorithms/sorting/bucket_sort.py b/algorithms/sorting/bucket_sort.py index e256b271a..2ac6d12ed 100644 --- a/algorithms/sorting/bucket_sort.py +++ b/algorithms/sorting/bucket_sort.py @@ -1,5 +1,4 @@ -""" -Bucket Sort +"""Bucket Sort. Bucket sort distributes elements into a number of buckets, sorts each bucket individually (here using insertion sort), and then concatenates @@ -27,6 +26,7 @@ def bucket_sort(array: list[int]) -> list[int]: Examples: >>> bucket_sort([3, 1, 2, 4]) [1, 2, 3, 4] + """ num_buckets = len(array) buckets: list[list[int]] = [[] for _ in range(num_buckets)] diff --git a/algorithms/sorting/cocktail_shaker_sort.py b/algorithms/sorting/cocktail_shaker_sort.py index 66297550d..5b4f11c6d 100644 --- a/algorithms/sorting/cocktail_shaker_sort.py +++ b/algorithms/sorting/cocktail_shaker_sort.py @@ -1,5 +1,4 @@ -""" -Cocktail Shaker Sort +"""Cocktail Shaker Sort. Cocktail shaker sort is a variation of bubble sort that traverses the list alternately from left-to-right and right-to-left. @@ -26,6 +25,7 @@ def cocktail_shaker_sort(array: list[int]) -> list[int]: Examples: >>> cocktail_shaker_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) swapped = True diff --git a/algorithms/sorting/comb_sort.py b/algorithms/sorting/comb_sort.py index 79bf04053..3b9a9ec17 100644 --- a/algorithms/sorting/comb_sort.py +++ b/algorithms/sorting/comb_sort.py @@ -1,5 +1,4 @@ -""" -Comb Sort +"""Comb Sort. Comb sort improves on bubble sort by using a gap sequence that shrinks by a factor of approximately 1.3 on each pass, eliminating small values near @@ -27,6 +26,7 @@ def comb_sort(array: list[int]) -> list[int]: Examples: >>> comb_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) gap = n diff --git a/algorithms/sorting/counting_sort.py b/algorithms/sorting/counting_sort.py index a29e8fb07..e567e1e3b 100644 --- a/algorithms/sorting/counting_sort.py +++ b/algorithms/sorting/counting_sort.py @@ -1,5 +1,4 @@ -""" -Counting Sort +"""Counting Sort. Counting sort counts the occurrences of each value and uses cumulative counts to place each element in its correct position. It supports @@ -27,6 +26,7 @@ def counting_sort(array: list[int]) -> list[int]: Examples: >>> counting_sort([3, 1, 2]) [1, 2, 3] + """ min_value = min(array) offset = -min_value if min_value < 0 else 0 diff --git a/algorithms/sorting/cycle_sort.py b/algorithms/sorting/cycle_sort.py index 26b829245..fd7d3cfa8 100644 --- a/algorithms/sorting/cycle_sort.py +++ b/algorithms/sorting/cycle_sort.py @@ -1,5 +1,4 @@ -""" -Cycle Sort +"""Cycle Sort. Cycle sort decomposes the permutation into cycles and rotates each cycle to produce a sorted result. It minimises the number of writes to the @@ -27,6 +26,7 @@ def cycle_sort(array: list[int]) -> list[int]: Examples: >>> cycle_sort([3, 1, 2]) [1, 2, 3] + """ length = len(array) diff --git a/algorithms/sorting/exchange_sort.py b/algorithms/sorting/exchange_sort.py index 231d0faf4..ffc5f71e8 100644 --- a/algorithms/sorting/exchange_sort.py +++ b/algorithms/sorting/exchange_sort.py @@ -1,5 +1,4 @@ -""" -Exchange Sort +"""Exchange Sort. Exchange sort compares every pair of elements and swaps them if they are out of order. It is conceptually similar to bubble sort. @@ -26,6 +25,7 @@ def exchange_sort(array: list[int]) -> list[int]: Examples: >>> exchange_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) for i in range(n - 1): diff --git a/algorithms/sorting/gnome_sort.py b/algorithms/sorting/gnome_sort.py index 0680ba44b..c56a19b8d 100644 --- a/algorithms/sorting/gnome_sort.py +++ b/algorithms/sorting/gnome_sort.py @@ -1,5 +1,4 @@ -""" -Gnome Sort +"""Gnome Sort. Gnome sort moves an element toward the front of the list until it finds an element that is smaller or equal, then steps forward again. It is @@ -27,6 +26,7 @@ def gnome_sort(array: list[int]) -> list[int]: Examples: >>> gnome_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) index = 0 diff --git a/algorithms/sorting/heap_sort.py b/algorithms/sorting/heap_sort.py index 6eb373b80..6d5392c8f 100644 --- a/algorithms/sorting/heap_sort.py +++ b/algorithms/sorting/heap_sort.py @@ -1,5 +1,4 @@ -""" -Heap Sort +"""Heap Sort. Heap sort builds a heap from the data and repeatedly extracts the extreme element to produce a sorted array. Two variants are provided: @@ -27,6 +26,7 @@ def max_heap_sort(array: list[int]) -> list[int]: Examples: >>> max_heap_sort([3, 1, 2]) [1, 2, 3] + """ for i in range(len(array) - 1, 0, -1): _max_heapify(array, i) @@ -64,6 +64,7 @@ def min_heap_sort(array: list[int]) -> list[int]: Examples: >>> min_heap_sort([3, 1, 2]) [1, 2, 3] + """ for i in range(len(array) - 1): _min_heapify(array, i) diff --git a/algorithms/sorting/insertion_sort.py b/algorithms/sorting/insertion_sort.py index a6f70a019..6dff4e79a 100644 --- a/algorithms/sorting/insertion_sort.py +++ b/algorithms/sorting/insertion_sort.py @@ -1,5 +1,4 @@ -""" -Insertion Sort +"""Insertion Sort. Insertion sort builds the sorted list one element at a time by repeatedly picking the next element and inserting it into its correct position. @@ -26,6 +25,7 @@ def insertion_sort(array: list[int]) -> list[int]: Examples: >>> insertion_sort([3, 1, 2]) [1, 2, 3] + """ for i in range(len(array)): cursor = array[i] diff --git a/algorithms/sorting/meeting_rooms.py b/algorithms/sorting/meeting_rooms.py index 7cca949cd..341b8d625 100644 --- a/algorithms/sorting/meeting_rooms.py +++ b/algorithms/sorting/meeting_rooms.py @@ -1,5 +1,4 @@ -""" -Meeting Rooms +"""Meeting Rooms. Given an array of meeting time intervals consisting of start and end times [[s1, e1], [s2, e2], ...] (si < ei), determine if a person could attend @@ -27,6 +26,7 @@ def can_attend_meetings(intervals: list) -> bool: Examples: >>> # With intervals [[0,30],[5,10],[15,20]] the answer is False. >>> # With intervals [[7,10],[2,4]] the answer is True. + """ intervals = sorted(intervals, key=lambda x: x.start) for i in range(1, len(intervals)): diff --git a/algorithms/sorting/merge_sort.py b/algorithms/sorting/merge_sort.py index e3f1628a7..7ee4da62d 100644 --- a/algorithms/sorting/merge_sort.py +++ b/algorithms/sorting/merge_sort.py @@ -1,5 +1,4 @@ -""" -Merge Sort +"""Merge Sort. Merge sort divides the array in half, recursively sorts each half, and then merges the two sorted halves back together. @@ -26,6 +25,7 @@ def merge_sort(array: list[int]) -> list[int]: Examples: >>> merge_sort([3, 1, 2]) [1, 2, 3] + """ if len(array) <= 1: return array @@ -45,6 +45,7 @@ def _merge(left: list[int], right: list[int], merged: list[int]) -> None: left: Sorted left half. right: Sorted right half. merged: Destination list (length == len(left) + len(right)). + """ left_cursor = 0 right_cursor = 0 diff --git a/algorithms/sorting/pancake_sort.py b/algorithms/sorting/pancake_sort.py index b4579dec2..0045cc692 100644 --- a/algorithms/sorting/pancake_sort.py +++ b/algorithms/sorting/pancake_sort.py @@ -1,5 +1,4 @@ -""" -Pancake Sort +"""Pancake Sort. Pancake sort sorts an array by repeatedly finding the maximum element in the unsorted portion, flipping it to the front, and then flipping the @@ -27,6 +26,7 @@ def pancake_sort(array: list[int]) -> list[int]: Examples: >>> pancake_sort([3, 1, 2]) [1, 2, 3] + """ if len(array) <= 1: return array diff --git a/algorithms/sorting/pigeonhole_sort.py b/algorithms/sorting/pigeonhole_sort.py index a48cb54cd..2a65f91ad 100644 --- a/algorithms/sorting/pigeonhole_sort.py +++ b/algorithms/sorting/pigeonhole_sort.py @@ -1,5 +1,4 @@ -""" -Pigeonhole Sort +"""Pigeonhole Sort. Pigeonhole sort is suitable for sorting lists where the number of elements and the range of possible key values are approximately equal. @@ -26,6 +25,7 @@ def pigeonhole_sort(array: list[int]) -> list[int]: Examples: >>> pigeonhole_sort([3, 1, 2]) [1, 2, 3] + """ max_value = max(array) min_value = min(array) diff --git a/algorithms/sorting/quick_sort.py b/algorithms/sorting/quick_sort.py index 62c9d0ecb..3bd8afe8d 100644 --- a/algorithms/sorting/quick_sort.py +++ b/algorithms/sorting/quick_sort.py @@ -1,5 +1,4 @@ -""" -Quick Sort +"""Quick Sort. Quick sort selects a pivot element, partitions the array around the pivot, and recursively sorts the two partitions. @@ -26,6 +25,7 @@ def quick_sort(array: list[int]) -> list[int]: Examples: >>> quick_sort([3, 1, 2]) [1, 2, 3] + """ _quick_sort_recursive(array, 0, len(array) - 1) return array @@ -44,6 +44,7 @@ def _partition(array: list[int], first: int, last: int) -> int: Returns: The final index of the pivot element. + """ wall = first for pos in range(first, last): diff --git a/algorithms/sorting/radix_sort.py b/algorithms/sorting/radix_sort.py index adb64f29e..5ef04195a 100644 --- a/algorithms/sorting/radix_sort.py +++ b/algorithms/sorting/radix_sort.py @@ -1,5 +1,4 @@ -""" -Radix Sort +"""Radix Sort. Radix sort processes digits from least significant to most significant, distributing elements into buckets for each digit and collecting them @@ -27,6 +26,7 @@ def radix_sort(array: list[int]) -> list[int]: Examples: >>> radix_sort([170, 45, 75, 90, 802, 24, 2, 66]) [2, 24, 45, 66, 75, 90, 170, 802] + """ position = 1 max_number = max(array) diff --git a/algorithms/sorting/selection_sort.py b/algorithms/sorting/selection_sort.py index 6f09e428e..5bcafdf57 100644 --- a/algorithms/sorting/selection_sort.py +++ b/algorithms/sorting/selection_sort.py @@ -1,5 +1,4 @@ -""" -Selection Sort +"""Selection Sort. Selection sort repeatedly selects the smallest element from the unsorted portion and moves it to the end of the sorted portion. @@ -26,6 +25,7 @@ def selection_sort(array: list[int]) -> list[int]: Examples: >>> selection_sort([3, 1, 2]) [1, 2, 3] + """ for i in range(len(array)): minimum = i diff --git a/algorithms/sorting/shell_sort.py b/algorithms/sorting/shell_sort.py index 91f4fbc25..a14f76234 100644 --- a/algorithms/sorting/shell_sort.py +++ b/algorithms/sorting/shell_sort.py @@ -1,5 +1,4 @@ -""" -Shell Sort +"""Shell Sort. Shell sort is a generalisation of insertion sort that allows the exchange of elements that are far apart. The gap between compared elements is @@ -28,6 +27,7 @@ def shell_sort(array: list[int]) -> list[int]: Examples: >>> shell_sort([3, 1, 2]) [1, 2, 3] + """ n = len(array) gap = n // 2 diff --git a/algorithms/sorting/sort_colors.py b/algorithms/sorting/sort_colors.py index cfc814a88..4c8a952b7 100644 --- a/algorithms/sorting/sort_colors.py +++ b/algorithms/sorting/sort_colors.py @@ -1,5 +1,4 @@ -""" -Sort Colors (Dutch National Flag) +"""Sort Colors (Dutch National Flag). Given an array with n objects colored red, white, or blue (represented by 0, 1, and 2), sort them in-place so that objects of the same color are @@ -27,6 +26,7 @@ def sort_colors(array: list[int]) -> list[int]: Examples: >>> sort_colors([2, 0, 1, 2, 1, 0]) [0, 0, 1, 1, 2, 2] + """ red = white = 0 for k in range(len(array)): diff --git a/algorithms/sorting/stooge_sort.py b/algorithms/sorting/stooge_sort.py index c13c521ec..e26da71fb 100644 --- a/algorithms/sorting/stooge_sort.py +++ b/algorithms/sorting/stooge_sort.py @@ -1,5 +1,4 @@ -""" -Stooge Sort +"""Stooge Sort. Stooge sort is a recursive sorting algorithm notable for its unusually bad time complexity. It works by recursively sorting the first 2/3, then @@ -27,6 +26,7 @@ def stooge_sort(array: list[int]) -> list[int]: Examples: >>> stooge_sort([3, 1, 2]) [1, 2, 3] + """ _stooge_sort(array, 0, len(array) - 1) return array diff --git a/algorithms/sorting/wiggle_sort.py b/algorithms/sorting/wiggle_sort.py index 1769e7fcb..d28b29199 100644 --- a/algorithms/sorting/wiggle_sort.py +++ b/algorithms/sorting/wiggle_sort.py @@ -1,5 +1,4 @@ -""" -Wiggle Sort +"""Wiggle Sort. Given an unsorted array, reorder it in-place such that nums[0] < nums[1] > nums[2] < nums[3] ... @@ -26,6 +25,7 @@ def wiggle_sort(array: list[int]) -> list[int]: Examples: >>> wiggle_sort([3, 5, 2, 1, 6, 4]) [3, 5, 1, 6, 2, 4] + """ for i in range(len(array)): if (i % 2 == 1) == (array[i - 1] > array[i]): diff --git a/algorithms/stack/is_consecutive.py b/algorithms/stack/is_consecutive.py index 51f08f071..9635c063c 100644 --- a/algorithms/stack/is_consecutive.py +++ b/algorithms/stack/is_consecutive.py @@ -1,5 +1,4 @@ -""" -Is Consecutive +"""Is Consecutive. Check whether a stack contains a sequence of consecutive integers starting from the bottom. Two approaches are provided: one using an @@ -31,6 +30,7 @@ def first_is_consecutive(stack: list[int]) -> bool: True >>> first_is_consecutive([3, 4, 6, 7]) False + """ storage_stack: list[int] = [] for _ in range(len(stack)): @@ -62,6 +62,7 @@ def second_is_consecutive(stack: list[int]) -> bool: True >>> second_is_consecutive([3, 4, 6, 7]) False + """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): diff --git a/algorithms/stack/is_sorted.py b/algorithms/stack/is_sorted.py index 0407eaafc..fa8b4b5be 100644 --- a/algorithms/stack/is_sorted.py +++ b/algorithms/stack/is_sorted.py @@ -1,5 +1,4 @@ -""" -Is Sorted +"""Is Sorted. Check whether a stack is sorted in ascending order from bottom to top using a single auxiliary stack. @@ -33,6 +32,7 @@ def is_sorted(stack: list[int]) -> bool: True >>> stack # original stack is preserved on True [1, 2, 3] + """ storage_stack: list[int] = [] for _ in range(len(stack)): diff --git a/algorithms/stack/longest_abs_path.py b/algorithms/stack/longest_abs_path.py index 75ba00a5f..49e120d5a 100644 --- a/algorithms/stack/longest_abs_path.py +++ b/algorithms/stack/longest_abs_path.py @@ -1,5 +1,4 @@ -""" -Longest Absolute File Path +"""Longest Absolute File Path. Given a string representing a file system in a special format, find the length of the longest absolute path to a file. Directories and files @@ -16,7 +15,7 @@ def length_longest_path(input_str: str) -> int: - """Find the length of the longest absolute path to a file. + r"""Find the length of the longest absolute path to a file. Args: input_str: A string encoding the file system structure using @@ -28,6 +27,7 @@ def length_longest_path(input_str: str) -> int: Examples: >>> length_longest_path("dir\\n\\tfile.txt") 12 + """ current_length = 0 max_length = 0 diff --git a/algorithms/stack/ordered_stack.py b/algorithms/stack/ordered_stack.py index 655b5be0d..b7045a0df 100644 --- a/algorithms/stack/ordered_stack.py +++ b/algorithms/stack/ordered_stack.py @@ -1,5 +1,4 @@ -""" -Ordered Stack +"""Ordered Stack. A stack that maintains elements in sorted order, with the highest value at the top and the lowest at the bottom. Push operations preserve the @@ -25,6 +24,7 @@ class OrderedStack: >>> s.push(2) >>> s.pop() 3 + """ def __init__(self) -> None: @@ -36,6 +36,7 @@ def is_empty(self) -> bool: Returns: True if the stack has no elements. + """ return self.items == [] @@ -44,6 +45,7 @@ def _push_direct(self, item: int) -> None: Args: item: The value to append. + """ self.items.append(item) @@ -52,6 +54,7 @@ def push(self, item: int) -> None: Args: item: The value to push. + """ temp_stack = OrderedStack() if self.is_empty() or item > self.peek(): @@ -71,9 +74,11 @@ def pop(self) -> int: Raises: IndexError: If the stack is empty. + """ if self.is_empty(): - raise IndexError("Stack is empty") + msg = "Stack is empty" + raise IndexError(msg) return self.items.pop() def peek(self) -> int: @@ -81,6 +86,7 @@ def peek(self) -> int: Returns: The top (largest) element. + """ return self.items[len(self.items) - 1] @@ -89,5 +95,6 @@ def size(self) -> int: Returns: The stack size. + """ return len(self.items) diff --git a/algorithms/stack/remove_min.py b/algorithms/stack/remove_min.py index ae37eda9d..475c4fb83 100644 --- a/algorithms/stack/remove_min.py +++ b/algorithms/stack/remove_min.py @@ -1,5 +1,4 @@ -""" -Remove Min from Stack +"""Remove Min from Stack. Remove the smallest value from a stack, preserving the relative order of the remaining elements. @@ -26,6 +25,7 @@ def remove_min(stack: list[int]) -> list[int]: Examples: >>> remove_min([2, 8, 3, -6, 7, 3]) [2, 8, 3, 7, 3] + """ storage_stack: list[int] = [] if len(stack) == 0: @@ -34,8 +34,7 @@ def remove_min(stack: list[int]) -> list[int]: stack.append(minimum) for _ in range(len(stack)): val = stack.pop() - if val <= minimum: - minimum = val + minimum = min(val, minimum) storage_stack.append(val) for _ in range(len(storage_stack)): val = storage_stack.pop() diff --git a/algorithms/stack/simplify_path.py b/algorithms/stack/simplify_path.py index d8243730e..5316e7aed 100644 --- a/algorithms/stack/simplify_path.py +++ b/algorithms/stack/simplify_path.py @@ -1,5 +1,4 @@ -""" -Simplify Path +"""Simplify Path. Given an absolute Unix-style file path, simplify it by resolving '.' (current directory), '..' (parent directory), and multiple slashes. @@ -28,14 +27,15 @@ def simplify_path(path: str) -> str: '/home' >>> simplify_path("/a/./b/../../c/") '/c' + """ skip = {"..", ".", ""} stack: list[str] = [] tokens = path.split("/") - for token in tokens: - if token == "..": + for part in tokens: + if part == "..": if stack: stack.pop() - elif token not in skip: - stack.append(token) + elif part not in skip: + stack.append(part) return "/" + "/".join(stack) diff --git a/algorithms/stack/stutter.py b/algorithms/stack/stutter.py index 95817888f..258e86327 100644 --- a/algorithms/stack/stutter.py +++ b/algorithms/stack/stutter.py @@ -1,5 +1,4 @@ -""" -Stutter +"""Stutter. Replace every value in a stack with two occurrences of that value. Two approaches: one using an auxiliary stack, one using an auxiliary queue. @@ -28,6 +27,7 @@ def first_stutter(stack: list[int]) -> list[int]: Examples: >>> first_stutter([3, 7, 1, 14, 9]) [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] + """ storage_stack: list[int] = [] for _ in range(len(stack)): @@ -52,6 +52,7 @@ def second_stutter(stack: list[int]) -> list[int]: Examples: >>> second_stutter([3, 7, 1, 14, 9]) [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] + """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): diff --git a/algorithms/stack/switch_pairs.py b/algorithms/stack/switch_pairs.py index 71062b908..5031dd8c4 100644 --- a/algorithms/stack/switch_pairs.py +++ b/algorithms/stack/switch_pairs.py @@ -1,5 +1,4 @@ -""" -Switch Pairs +"""Switch Pairs. Switch successive pairs of values in a stack starting from the bottom. If there is an odd number of values, the top element is not moved. @@ -29,6 +28,7 @@ def first_switch_pairs(stack: list[int]) -> list[int]: Examples: >>> first_switch_pairs([3, 8, 17, 9, 1, 10]) [8, 3, 9, 17, 10, 1] + """ storage_stack: list[int] = [] for _ in range(len(stack)): @@ -56,6 +56,7 @@ def second_switch_pairs(stack: list[int]) -> list[int]: Examples: >>> second_switch_pairs([3, 8, 17, 9, 1, 10]) [8, 3, 9, 17, 10, 1] + """ queue: collections.deque[int] = collections.deque() for _ in range(len(stack)): diff --git a/algorithms/stack/valid_parenthesis.py b/algorithms/stack/valid_parenthesis.py index 6aba6a839..69aa2c078 100644 --- a/algorithms/stack/valid_parenthesis.py +++ b/algorithms/stack/valid_parenthesis.py @@ -1,5 +1,4 @@ -""" -Valid Parentheses +"""Valid Parentheses. Determine if a string containing only '(', ')', '{', '}', '[' and ']' has valid (properly closed and nested) brackets. @@ -28,6 +27,7 @@ def is_valid(s: str) -> bool: True >>> is_valid("(]") False + """ stack: list[str] = [] matching = {")": "(", "}": "{", "]": "["} diff --git a/algorithms/streaming/misra_gries.py b/algorithms/streaming/misra_gries.py index a8c0a98a7..d026a4fb4 100644 --- a/algorithms/streaming/misra_gries.py +++ b/algorithms/streaming/misra_gries.py @@ -1,5 +1,4 @@ -""" -Misra-Gries Frequency Estimation +"""Misra-Gries Frequency Estimation. Given a list of items and a value k, returns every item that appears at least n/k times, where n is the length of the list. Defaults to k=2 (majority @@ -33,6 +32,7 @@ def misras_gries(array: list[int], k: int = 2) -> dict[str, int] | None: {'1': 4} >>> misras_gries([0, 0, 0, 0, 1, 1, 1, 2, 2], 3) {'0': 4, '1': 3} + """ keys: dict[str, int] = {} for item in array: @@ -54,7 +54,7 @@ def misras_gries(array: list[int], k: int = 2) -> dict[str, int] | None: if freq >= len(array) / k: frequencies[suspect] = freq - return frequencies if frequencies else None + return frequencies or None def _count_frequency(array: list[int], element: int) -> int: @@ -66,5 +66,6 @@ def _count_frequency(array: list[int], element: int) -> int: Returns: The number of occurrences. + """ return array.count(element) diff --git a/algorithms/streaming/one_sparse_recovery.py b/algorithms/streaming/one_sparse_recovery.py index aaa3ec30d..8d396a902 100644 --- a/algorithms/streaming/one_sparse_recovery.py +++ b/algorithms/streaming/one_sparse_recovery.py @@ -1,5 +1,4 @@ -""" -Non-negative 1-Sparse Recovery +"""Non-negative 1-Sparse Recovery. Determines whether a dynamic stream of (value, sign) tuples is 1-sparse, meaning all values cancel out except for a single unique number. If so, @@ -29,6 +28,7 @@ def one_sparse(array: list[tuple[int, str]]) -> int | None: 4 >>> one_sparse([(2, '+'), (2, '+'), (2, '+'), ... (2, '+'), (2, '+'), (2, '+'), (1, '+')]) + """ sum_signs = 0 bitsum: list[int] = [0] * 32 @@ -57,8 +57,9 @@ def _check_bit_sum_consistency(bitsum: list[int], sum_signs: int) -> bool: Returns: True if the bitsum is consistent with a 1-sparse stream. + """ - return all(val == 0 or val == sum_signs for val in bitsum) + return all(val in (0, sum_signs) for val in bitsum) def _update_bit_sum(bitsum: list[int], val: int, sign: str) -> None: @@ -68,6 +69,7 @@ def _update_bit_sum(bitsum: list[int], val: int, sign: str) -> None: bitsum: The accumulated bit sums to update in place. val: The integer value whose bits to process. sign: '+' to add or '-' to subtract. + """ idx = 0 if sign == "+": diff --git a/algorithms/string/__init__.py b/algorithms/string/__init__.py index 7e877371e..9021750f8 100644 --- a/algorithms/string/__init__.py +++ b/algorithms/string/__init__.py @@ -77,11 +77,15 @@ from algorithms.string.z_algorithm import compute_z_array, z_search __all__ = [ + "RollingHash", "add_binary", + "alphabet_board_path", "atbash", "bracket", "caesar_cipher", + "can_swap_to_equal", "check_pangram", + "compute_z_array", "contain_string", "convert_morse_word", "count_binary_substring", @@ -119,6 +123,7 @@ "longest_common_prefix_v3", "longest_palindrome", "make_sentence", + "manacher", "match_symbol", "match_symbol_1", "min_distance", @@ -132,21 +137,16 @@ "repeat_substring", "reverse_vowel", "reverse_words", - "RollingHash", "roman_to_int", "rotate", "rotate_alt", - "strong_password", "strip_url_params1", "strip_url_params2", "strip_url_params3", + "strong_password", "text_justification", "ultra_pythonic", "unique_morse", "word_squares", "z_search", - "compute_z_array", - "alphabet_board_path", - "manacher", - "can_swap_to_equal", ] diff --git a/algorithms/string/add_binary.py b/algorithms/string/add_binary.py index bdcc4e81a..e0cf37a5c 100644 --- a/algorithms/string/add_binary.py +++ b/algorithms/string/add_binary.py @@ -1,5 +1,4 @@ -""" -Add Binary +"""Add Binary. Given two binary strings, return their sum as a binary string. @@ -26,6 +25,7 @@ def add_binary(first: str, second: str) -> str: Examples: >>> add_binary("11", "1") '100' + """ result = "" carry, index_a, index_b = 0, len(first) - 1, len(second) - 1 diff --git a/algorithms/string/atbash_cipher.py b/algorithms/string/atbash_cipher.py index eda406982..7441fbd30 100644 --- a/algorithms/string/atbash_cipher.py +++ b/algorithms/string/atbash_cipher.py @@ -1,5 +1,4 @@ -""" -Atbash Cipher +"""Atbash Cipher. Atbash cipher maps each letter of the alphabet to its reverse. The first letter 'a' maps to 'z', 'b' maps to 'y', and so on. @@ -26,6 +25,7 @@ def atbash(text: str) -> str: Examples: >>> atbash("abcdefghijklmno") 'zyxwvutsrqponml' + """ translated = "" for char in text: diff --git a/algorithms/string/breaking_bad.py b/algorithms/string/breaking_bad.py index 06396386f..881a5a8f5 100644 --- a/algorithms/string/breaking_bad.py +++ b/algorithms/string/breaking_bad.py @@ -1,5 +1,4 @@ -""" -Breaking Bad Symbol Matching +"""Breaking Bad Symbol Matching. Given an array of words and an array of symbols, display each word with its matched symbol surrounded by square brackets. If a word matches more than one @@ -31,6 +30,7 @@ def match_symbol(words: list[str], symbols: list[str]) -> list[str]: Examples: >>> match_symbol(['Google'], ['le']) ['Goog[le]'] + """ combined = [] for symbol in symbols: @@ -54,9 +54,10 @@ def match_symbol_1(words: list[str], symbols: list[str]) -> list[str]: Examples: >>> match_symbol_1(['Microsoft'], ['i', 'cro']) ['Mi[cro]soft'] + """ result = [] - symbols = sorted(symbols, key=lambda item: len(item), reverse=True) + symbols = sorted(symbols, key=len, reverse=True) for word in words: word_replaced = "" for symbol in symbols: @@ -90,6 +91,7 @@ def bracket(words: list[str], symbols: list[str]) -> tuple[str, ...]: Examples: >>> bracket(['Amazon', 'Microsoft', 'Google'], ['Am', 'cro', 'le']) ('[Am]azon', 'Mi[cro]soft', 'Goog[le]') + """ root = _TrieNode() for symbol in symbols: @@ -110,7 +112,7 @@ def bracket(words: list[str], symbols: list[str]) -> tuple[str, ...]: node = node.children[word[cursor]] if node.symbol is not None: symbol_list.append( - (cursor + 1 - len(node.symbol), cursor + 1, node.symbol) + (cursor + 1 - len(node.symbol), cursor + 1, node.symbol), ) cursor += 1 index += 1 diff --git a/algorithms/string/caesar_cipher.py b/algorithms/string/caesar_cipher.py index 767251c92..a471fe3e1 100644 --- a/algorithms/string/caesar_cipher.py +++ b/algorithms/string/caesar_cipher.py @@ -1,5 +1,4 @@ -""" -Caesar Cipher +"""Caesar Cipher. Caesar's cipher shifts each letter by a fixed number of positions in the alphabet. Letters wrap around when they pass the end of the alphabet. @@ -27,6 +26,7 @@ def caesar_cipher(text: str, shift: int) -> str: Examples: >>> caesar_cipher("Hello_World!", 4) 'Lipps_Asvph!' + """ result = "" for char in text: diff --git a/algorithms/string/check_pangram.py b/algorithms/string/check_pangram.py index 7cce209ed..fa01375b5 100644 --- a/algorithms/string/check_pangram.py +++ b/algorithms/string/check_pangram.py @@ -1,5 +1,4 @@ -""" -Check Pangram +"""Check Pangram. Checks whether a given string is a pangram, meaning it contains every letter of the English alphabet at least once. @@ -26,6 +25,7 @@ def check_pangram(input_string: str) -> bool: Examples: >>> check_pangram("The quick brown fox jumps over the lazy dog") True + """ alphabet = "abcdefghijklmnopqrstuvwxyz" return all(char in input_string.lower() for char in alphabet) diff --git a/algorithms/string/contain_string.py b/algorithms/string/contain_string.py index 2ded65854..ec5f81a4b 100644 --- a/algorithms/string/contain_string.py +++ b/algorithms/string/contain_string.py @@ -1,5 +1,4 @@ -""" -Contain String (strStr) +"""Contain String (strStr). Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. @@ -27,6 +26,7 @@ def contain_string(haystack: str, needle: str) -> int: Examples: >>> contain_string("hello", "ll") 2 + """ if len(needle) == 0: return 0 diff --git a/algorithms/string/count_binary_substring.py b/algorithms/string/count_binary_substring.py index 90e4e2d84..a629d3d74 100644 --- a/algorithms/string/count_binary_substring.py +++ b/algorithms/string/count_binary_substring.py @@ -1,5 +1,4 @@ -""" -Count Binary Substrings +"""Count Binary Substrings. Count the number of non-empty contiguous substrings that have the same number of 0s and 1s, where all 0s and all 1s are grouped consecutively. @@ -26,6 +25,7 @@ def count_binary_substring(text: str) -> int: Examples: >>> count_binary_substring("00110011") 6 + """ current = 1 previous = 0 @@ -37,5 +37,4 @@ def count_binary_substring(text: str) -> int: current = 1 else: current = current + 1 - count = count + min(previous, current) - return count + return count + min(previous, current) diff --git a/algorithms/string/decode_string.py b/algorithms/string/decode_string.py index 10e97250a..8144c793e 100644 --- a/algorithms/string/decode_string.py +++ b/algorithms/string/decode_string.py @@ -1,5 +1,4 @@ -""" -Decode String +"""Decode String. Given an encoded string, return its decoded string. The encoding rule is k[encoded_string], where the encoded_string inside the brackets is repeated @@ -27,6 +26,7 @@ def decode_string(text: str) -> str: Examples: >>> decode_string("3[a]2[bc]") 'aaabcbc' + """ stack: list[tuple[str, int]] = [] current_num = 0 diff --git a/algorithms/string/delete_reoccurring.py b/algorithms/string/delete_reoccurring.py index 6b2107d72..06735fe45 100644 --- a/algorithms/string/delete_reoccurring.py +++ b/algorithms/string/delete_reoccurring.py @@ -1,5 +1,4 @@ -""" -Delete Reoccurring Characters +"""Delete Reoccurring Characters. Given a string, delete any reoccurring characters and return the new string containing only the first occurrence of each character. @@ -26,6 +25,7 @@ def delete_reoccurring_characters(string: str) -> str: Examples: >>> delete_reoccurring_characters("aaabcccc") 'abc' + """ seen_characters: set[str] = set() output_string = "" diff --git a/algorithms/string/domain_extractor.py b/algorithms/string/domain_extractor.py index 0d7835f52..f8c1549db 100644 --- a/algorithms/string/domain_extractor.py +++ b/algorithms/string/domain_extractor.py @@ -1,5 +1,4 @@ -""" -Domain Name Extractor +"""Domain Name Extractor. Given a URL as a string, parse out just the domain name and return it. Uses only the .split() built-in function without regex or urlparse. @@ -26,8 +25,9 @@ def domain_name_1(url: str) -> str: Examples: >>> domain_name_1("https://github.com/SaadBenn") 'github' + """ - full_domain_name = url.split("//")[-1] + full_domain_name = url.rsplit("//", maxsplit=1)[-1] actual_domain = full_domain_name.split(".") if len(actual_domain) > 2: return actual_domain[1] @@ -46,5 +46,10 @@ def domain_name_2(url: str) -> str: Examples: >>> domain_name_2("http://google.com") 'google' + """ - return url.split("//")[-1].split("www.")[-1].split(".")[0] + return ( + url.rsplit("//", maxsplit=1)[-1] + .rsplit("www.", maxsplit=1)[-1] + .split(".", maxsplit=1)[0] + ) diff --git a/algorithms/string/encode_decode.py b/algorithms/string/encode_decode.py index 227bdb344..631d2f004 100644 --- a/algorithms/string/encode_decode.py +++ b/algorithms/string/encode_decode.py @@ -1,5 +1,4 @@ -""" -Encode and Decode Strings +"""Encode and Decode Strings. Design an algorithm to encode a list of strings to a single string, and decode it back to the original list of strings. @@ -26,6 +25,7 @@ def encode(strs: str) -> str: Examples: >>> encode("keon is awesome") '4:keon2:is7:awesome' + """ result = "" for word in strs.split(): @@ -45,6 +45,7 @@ def decode(text: str) -> list[str]: Examples: >>> decode("4:keon2:is7:awesome") ['keon', 'is', 'awesome'] + """ words: list[str] = [] index = 0 diff --git a/algorithms/string/first_unique_char.py b/algorithms/string/first_unique_char.py index 468874505..e97851851 100644 --- a/algorithms/string/first_unique_char.py +++ b/algorithms/string/first_unique_char.py @@ -1,5 +1,4 @@ -""" -First Unique Character in a String +"""First Unique Character in a String. Given a string, find the first non-repeating character and return its index. If no unique character exists, return -1. @@ -26,6 +25,7 @@ def first_unique_char(text: str) -> int: Examples: >>> first_unique_char("leetcode") 0 + """ if len(text) == 1: return 0 @@ -36,6 +36,5 @@ def first_unique_char(text: str) -> int: and text[index] not in banned ): return index - else: - banned.append(text[index]) + banned.append(text[index]) return -1 diff --git a/algorithms/string/fizzbuzz.py b/algorithms/string/fizzbuzz.py index 47d31c30d..0be417acd 100644 --- a/algorithms/string/fizzbuzz.py +++ b/algorithms/string/fizzbuzz.py @@ -1,5 +1,4 @@ -""" -FizzBuzz +"""FizzBuzz. Return an array of numbers from 1 to N, replacing multiples of 3 with 'Fizz', multiples of 5 with 'Buzz', and multiples of both with 'FizzBuzz'. @@ -31,11 +30,14 @@ def fizzbuzz(number: int) -> list[int | str]: Examples: >>> fizzbuzz(5) [1, 2, 'Fizz', 4, 'Buzz'] + """ if number < 1: - raise ValueError("n cannot be less than one") + msg = "n cannot be less than one" + raise ValueError(msg) if number is None: - raise TypeError("n cannot be None") + msg = "n cannot be None" + raise TypeError(msg) result: list[int | str] = [] for value in range(1, number + 1): @@ -62,6 +64,7 @@ def fizzbuzz_with_helper_func(number: int) -> list[int | str]: Examples: >>> fizzbuzz_with_helper_func(3) [1, 2, 'Fizz'] + """ return [_fb(value) for value in range(1, number + 1)] @@ -74,6 +77,7 @@ def _fb(value: int) -> int | str: Returns: 'Fizz', 'Buzz', 'FizzBuzz', or the number itself. + """ result = (value % 3 == 0) * "Fizz" + (value % 5 == 0) * "Buzz" return result if result != "" else value diff --git a/algorithms/string/group_anagrams.py b/algorithms/string/group_anagrams.py index 348105fe9..03dce14e2 100644 --- a/algorithms/string/group_anagrams.py +++ b/algorithms/string/group_anagrams.py @@ -1,5 +1,4 @@ -""" -Group Anagrams +"""Group Anagrams. Given an array of strings, group anagrams together. Anagrams are words that contain the same letters in a different order. @@ -26,6 +25,7 @@ def group_anagrams(strings: list[str]) -> list[list[str]]: Examples: >>> group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]) [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']] + """ anagram_map: dict[str, int] = {} groups: list[list[str]] = [] diff --git a/algorithms/string/int_to_roman.py b/algorithms/string/int_to_roman.py index 2ae3bef88..d056e008b 100644 --- a/algorithms/string/int_to_roman.py +++ b/algorithms/string/int_to_roman.py @@ -1,5 +1,4 @@ -""" -Integer to Roman Numeral +"""Integer to Roman Numeral. Given an integer, convert it to a Roman numeral string. Input is guaranteed to be within the range from 1 to 3999. @@ -26,6 +25,7 @@ def int_to_roman(num: int) -> str: Examples: >>> int_to_roman(644) 'DCXLIV' + """ thousands = ["", "M", "MM", "MMM"] hundreds = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"] diff --git a/algorithms/string/is_palindrome.py b/algorithms/string/is_palindrome.py index 8563fb0ea..820f14772 100644 --- a/algorithms/string/is_palindrome.py +++ b/algorithms/string/is_palindrome.py @@ -1,5 +1,4 @@ -""" -Is Palindrome +"""Is Palindrome. Determine if a string is a palindrome, considering only alphanumeric characters and ignoring cases. Multiple approaches are provided. @@ -34,6 +33,7 @@ def is_palindrome(text: str) -> bool: True >>> is_palindrome("a1b2a") False + """ left = 0 right = len(text) - 1 @@ -56,6 +56,7 @@ def _remove_punctuation(text: str) -> str: Returns: A lowercase string containing only alphanumeric characters. + """ return "".join(char.lower() for char in text if char.isalnum()) @@ -68,6 +69,7 @@ def _string_reverse(text: str) -> str: Returns: The reversed string. + """ return text[::-1] @@ -90,6 +92,7 @@ def is_palindrome_reverse(text: str) -> bool: True >>> is_palindrome_reverse("a1b2a") False + """ text = _remove_punctuation(text) return text == _string_reverse(text) @@ -113,9 +116,10 @@ def is_palindrome_two_pointer(text: str) -> bool: True >>> is_palindrome_two_pointer("a1b2a") False + """ text = _remove_punctuation(text) - for index in range(0, len(text) // 2): + for index in range(len(text) // 2): if text[index] != text[len(text) - index - 1]: return False return True @@ -139,12 +143,13 @@ def is_palindrome_stack(text: str) -> bool: True >>> is_palindrome_stack("a1b2a") False + """ stack: list[str] = [] text = _remove_punctuation(text) for index in range(len(text) // 2, len(text)): stack.append(text[index]) - return all(text[index] == stack.pop() for index in range(0, len(text) // 2)) + return all(text[index] == stack.pop() for index in range(len(text) // 2)) def is_palindrome_deque(text: str) -> bool: @@ -165,6 +170,7 @@ def is_palindrome_deque(text: str) -> bool: True >>> is_palindrome_deque("a1b2a") False + """ text = _remove_punctuation(text) character_deque: deque[str] = deque() diff --git a/algorithms/string/is_rotated.py b/algorithms/string/is_rotated.py index f3553c895..a8bab0343 100644 --- a/algorithms/string/is_rotated.py +++ b/algorithms/string/is_rotated.py @@ -1,5 +1,4 @@ -""" -Is Rotated String +"""Is Rotated String. Given two strings, determine if the second is a rotated version of the first. Two approaches are provided: concatenation check and brute force. @@ -27,11 +26,11 @@ def is_rotated(first: str, second: str) -> bool: Examples: >>> is_rotated("hello", "llohe") True + """ if len(first) == len(second): return second in first + first - else: - return False + return False def is_rotated_v1(first: str, second: str) -> bool: @@ -47,6 +46,7 @@ def is_rotated_v1(first: str, second: str) -> bool: Examples: >>> is_rotated_v1("hello", "llohe") True + """ if len(first) != len(second): return False diff --git a/algorithms/string/judge_circle.py b/algorithms/string/judge_circle.py index 27fadadc1..234aa7b91 100644 --- a/algorithms/string/judge_circle.py +++ b/algorithms/string/judge_circle.py @@ -1,5 +1,4 @@ -""" -Judge Route Circle +"""Judge Route Circle. Given a sequence of robot moves (R, L, U, D), determine whether the robot returns to its starting position after completing all moves. @@ -26,6 +25,7 @@ def judge_circle(moves: str) -> bool: Examples: >>> judge_circle("UD") True + """ move_counts = { "U": 0, diff --git a/algorithms/string/knuth_morris_pratt.py b/algorithms/string/knuth_morris_pratt.py index 86ee68607..1f502212d 100644 --- a/algorithms/string/knuth_morris_pratt.py +++ b/algorithms/string/knuth_morris_pratt.py @@ -1,5 +1,4 @@ -""" -Knuth-Morris-Pratt String Search +"""Knuth-Morris-Pratt String Search. Given two sequences (text and pattern), return the list of start indexes in text that match the pattern using the KMP algorithm. @@ -13,7 +12,10 @@ from __future__ import annotations -from collections.abc import Sequence +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence def knuth_morris_pratt(text: Sequence, pattern: Sequence) -> list[int]: @@ -29,6 +31,7 @@ def knuth_morris_pratt(text: Sequence, pattern: Sequence) -> list[int]: Examples: >>> knuth_morris_pratt('hello there hero!', 'he') [0, 7, 12] + """ text_length = len(text) pattern_length = len(pattern) diff --git a/algorithms/string/license_number.py b/algorithms/string/license_number.py index 7495e5832..2fbd38b81 100644 --- a/algorithms/string/license_number.py +++ b/algorithms/string/license_number.py @@ -1,5 +1,4 @@ -""" -License Key Formatting +"""License Key Formatting. Given a license key string and a group size k, reformat the key so that each group contains exactly k characters, separated by dashes. @@ -27,6 +26,7 @@ def license_number(key: str, group_size: int) -> str: Examples: >>> license_number("a-bc-dfd-df", 2) 'ab-cd-fd-df' + """ result: list[str] = [] alphanumeric: list[str] = [] diff --git a/algorithms/string/longest_common_prefix.py b/algorithms/string/longest_common_prefix.py index d2d2b8041..2a7c54659 100644 --- a/algorithms/string/longest_common_prefix.py +++ b/algorithms/string/longest_common_prefix.py @@ -1,5 +1,4 @@ -""" -Longest Common Prefix +"""Longest Common Prefix. Find the longest common prefix string amongst an array of strings. Three approaches: horizontal scanning, vertical scanning, and divide and conquer. @@ -23,6 +22,7 @@ def _common_prefix(first: str, second: str) -> str: Returns: The common prefix shared by both strings. + """ if not first or not second: return "" @@ -46,6 +46,7 @@ def longest_common_prefix_v1(strings: list[str]) -> str: Examples: >>> longest_common_prefix_v1(["flower", "flow", "flight"]) 'fl' + """ if not strings: return "" @@ -67,6 +68,7 @@ def longest_common_prefix_v2(strings: list[str]) -> str: Examples: >>> longest_common_prefix_v2(["flower", "flow", "flight"]) 'fl' + """ if not strings: return "" @@ -89,6 +91,7 @@ def longest_common_prefix_v3(strings: list[str]) -> str: Examples: >>> longest_common_prefix_v3(["flower", "flow", "flight"]) 'fl' + """ if not strings: return "" @@ -105,6 +108,7 @@ def _longest_common_recursive(strings: list[str], left: int, right: int) -> str: Returns: The longest common prefix for the partition. + """ if left == right: return strings[left] diff --git a/algorithms/string/longest_palindromic_substring.py b/algorithms/string/longest_palindromic_substring.py index 1cb74d7ee..5690a133e 100644 --- a/algorithms/string/longest_palindromic_substring.py +++ b/algorithms/string/longest_palindromic_substring.py @@ -1,5 +1,4 @@ -""" -Longest Palindromic Substring +"""Longest Palindromic Substring. Given a string, find the longest palindromic substring using Manacher's algorithm, which runs in linear time. @@ -26,6 +25,7 @@ def longest_palindrome(text: str) -> str: Examples: >>> longest_palindrome("cbbd") 'bb' + """ if len(text) < 2: return text diff --git a/algorithms/string/make_sentence.py b/algorithms/string/make_sentence.py index ab820d5a0..d6964c011 100644 --- a/algorithms/string/make_sentence.py +++ b/algorithms/string/make_sentence.py @@ -1,5 +1,4 @@ -""" -Make Sentence +"""Make Sentence. For a given string and dictionary, count how many sentences can be formed from the string such that all words are contained in the dictionary. @@ -31,11 +30,12 @@ def make_sentence(text_piece: str, dictionaries: list[str]) -> bool: Examples: >>> make_sentence("applet", ["", "app", "let", "t", "apple", "applet"]) True + """ global count if len(text_piece) == 0: return True - for index in range(0, len(text_piece)): + for index in range(len(text_piece)): prefix, suffix = text_piece[0:index], text_piece[index:] if (prefix in dictionaries and (suffix in dictionaries diff --git a/algorithms/string/merge_string_checker.py b/algorithms/string/merge_string_checker.py index e68d44eaf..f120813a2 100644 --- a/algorithms/string/merge_string_checker.py +++ b/algorithms/string/merge_string_checker.py @@ -1,5 +1,4 @@ -""" -Merge String Checker +"""Merge String Checker. Determine if a given string can be formed by interleaving two other strings, preserving the character order from each part. @@ -28,6 +27,7 @@ def is_merge_recursive(text: str, part1: str, part2: str) -> bool: Examples: >>> is_merge_recursive("codewars", "cdw", "oears") True + """ if not part1: return text == part2 @@ -55,6 +55,7 @@ def is_merge_iterative(text: str, part1: str, part2: str) -> bool: Examples: >>> is_merge_iterative("codewars", "cdw", "oears") True + """ tuple_list = [(text, part1, part2)] while tuple_list: @@ -64,7 +65,6 @@ def is_merge_iterative(text: str, part1: str, part2: str) -> bool: tuple_list.append((string[1:], first_part[1:], second_part)) if second_part and string[0] == second_part[0]: tuple_list.append((string[1:], first_part, second_part[1:])) - else: - if not first_part and not second_part: - return True + elif not first_part and not second_part: + return True return False diff --git a/algorithms/string/min_distance.py b/algorithms/string/min_distance.py index f7b4d166f..e9ef028fa 100644 --- a/algorithms/string/min_distance.py +++ b/algorithms/string/min_distance.py @@ -1,5 +1,4 @@ -""" -Minimum Edit Distance (Delete Operation) +"""Minimum Edit Distance (Delete Operation). Given two words, find the minimum number of steps required to make them the same, where each step deletes one character from either string. @@ -27,6 +26,7 @@ def min_distance(word1: str, word2: str) -> int: Examples: >>> min_distance("sea", "eat") 2 + """ return len(word1) + len(word2) - 2 * _lcs(word1, word2, len(word1), len(word2)) @@ -42,6 +42,7 @@ def _lcs(word1: str, word2: str, length1: int, length2: int) -> int: Returns: The length of the longest common subsequence. + """ if length1 == 0 or length2 == 0: return 0 @@ -66,6 +67,7 @@ def min_distance_dp(word1: str, word2: str) -> int: Examples: >>> min_distance_dp("sea", "eat") 2 + """ rows, cols = len(word1) + 1, len(word2) + 1 table = [[0 for _ in range(cols)] for _ in range(rows)] diff --git a/algorithms/string/multiply_strings.py b/algorithms/string/multiply_strings.py index d0e501281..cde679b41 100644 --- a/algorithms/string/multiply_strings.py +++ b/algorithms/string/multiply_strings.py @@ -1,5 +1,4 @@ -""" -Multiply Strings +"""Multiply Strings. Given two non-negative integers represented as strings, return their product as a string without using built-in BigInteger or direct integer conversion. @@ -27,6 +26,7 @@ def multiply(num1: str, num2: str) -> str: Examples: >>> multiply("23", "23") '529' + """ intermediate: list[int] = [] zero = ord("0") diff --git a/algorithms/string/one_edit_distance.py b/algorithms/string/one_edit_distance.py index f263a2c80..d07700fa4 100644 --- a/algorithms/string/one_edit_distance.py +++ b/algorithms/string/one_edit_distance.py @@ -1,5 +1,4 @@ -""" -One Edit Distance +"""One Edit Distance. Given two strings, determine if they are exactly one edit distance apart. An edit is an insertion, deletion, or replacement of a single character. @@ -27,6 +26,7 @@ def is_one_edit(source: str, target: str) -> bool: Examples: >>> is_one_edit("abc", "abd") True + """ if len(source) > len(target): return is_one_edit(target, source) @@ -54,6 +54,7 @@ def is_one_edit2(source: str, target: str) -> bool: Examples: >>> is_one_edit2("abc", "abd") True + """ source_length, target_length = len(source), len(target) if source_length > target_length: diff --git a/algorithms/string/panagram.py b/algorithms/string/panagram.py index 3a6431b71..811d1abdd 100644 --- a/algorithms/string/panagram.py +++ b/algorithms/string/panagram.py @@ -1,5 +1,4 @@ -""" -Panagram Checker +"""Panagram Checker. Check whether a given string is a panagram (a sentence using every letter of the English alphabet at least once). @@ -28,6 +27,7 @@ def panagram(string: str) -> bool: Examples: >>> panagram("the quick brown fox jumps over the lazy dog") True + """ letters = set(ascii_lowercase) for char in string: diff --git a/algorithms/string/rabin_karp.py b/algorithms/string/rabin_karp.py index 9f2c821c1..89b3f53c2 100644 --- a/algorithms/string/rabin_karp.py +++ b/algorithms/string/rabin_karp.py @@ -1,5 +1,4 @@ -""" -Rabin-Karp String Search +"""Rabin-Karp String Search. A string searching algorithm that uses hashing to find a pattern in text. Uses a rolling hash to efficiently compare the pattern hash with substrings. @@ -20,6 +19,7 @@ class RollingHash: Args: text: The text to compute the hash over. window_size: The size of the hash window. + """ def __init__(self, text: str, window_size: int) -> None: @@ -27,7 +27,7 @@ def __init__(self, text: str, window_size: int) -> None: self.hash = 0 self.window_size = window_size - for index in range(0, window_size): + for index in range(window_size): self.hash += (ord(self.text[index]) - ord("a") + 1) * ( 26 ** (window_size - index - 1) ) @@ -51,6 +51,7 @@ def window_text(self) -> str: Returns: The substring currently covered by the rolling hash window. + """ return self.text[self.window_start : self.window_end] @@ -68,6 +69,7 @@ def rabin_karp(word: str, text: str) -> int | None: Examples: >>> rabin_karp("abc", "zsnabckfkd") 3 + """ if word == "" or text == "": return None diff --git a/algorithms/string/repeat_string.py b/algorithms/string/repeat_string.py index 7eeb82384..f488c7a69 100644 --- a/algorithms/string/repeat_string.py +++ b/algorithms/string/repeat_string.py @@ -1,5 +1,4 @@ -""" -Repeated String Match +"""Repeated String Match. Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of the result. Return -1 if impossible. @@ -27,6 +26,7 @@ def repeat_string(base: str, target: str) -> int: Examples: >>> repeat_string("abcd", "cdabcdab") 3 + """ repetition_count = 1 repeated = base diff --git a/algorithms/string/repeat_substring.py b/algorithms/string/repeat_substring.py index 93ddb664d..2ab3330a2 100644 --- a/algorithms/string/repeat_substring.py +++ b/algorithms/string/repeat_substring.py @@ -1,5 +1,4 @@ -""" -Repeated Substring Pattern +"""Repeated Substring Pattern. Given a non-empty string, check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. @@ -26,6 +25,7 @@ def repeat_substring(text: str) -> bool: Examples: >>> repeat_substring("abab") True + """ doubled = (text + text)[1:-1] return text in doubled diff --git a/algorithms/string/reverse_string.py b/algorithms/string/reverse_string.py index 2764119e4..9155c4b9b 100644 --- a/algorithms/string/reverse_string.py +++ b/algorithms/string/reverse_string.py @@ -1,5 +1,4 @@ -""" -Reverse String +"""Reverse String. Reverse a string using four different approaches: recursive, iterative, pythonic (using reversed), and ultra-pythonic (using slicing). @@ -26,6 +25,7 @@ def recursive(text: str) -> str: Examples: >>> recursive("hello there") 'ereht olleh' + """ length = len(text) if length < 2: @@ -45,6 +45,7 @@ def iterative(text: str) -> str: Examples: >>> iterative("hello there") 'ereht olleh' + """ characters = list(text) left, right = 0, len(text) - 1 @@ -70,6 +71,7 @@ def pythonic(text: str) -> str: Examples: >>> pythonic("hello there") 'ereht olleh' + """ characters = list(reversed(text)) return "".join(characters) @@ -87,5 +89,6 @@ def ultra_pythonic(text: str) -> str: Examples: >>> ultra_pythonic("hello there") 'ereht olleh' + """ return text[::-1] diff --git a/algorithms/string/reverse_vowel.py b/algorithms/string/reverse_vowel.py index fec301757..5e615df6d 100644 --- a/algorithms/string/reverse_vowel.py +++ b/algorithms/string/reverse_vowel.py @@ -1,5 +1,4 @@ -""" -Reverse Vowels of a String +"""Reverse Vowels of a String. Given a string, reverse only the vowels while keeping all other characters in their original positions. @@ -26,6 +25,7 @@ def reverse_vowel(text: str) -> str: Examples: >>> reverse_vowel("hello") 'holle' + """ vowels = "AEIOUaeiou" left, right = 0, len(text) - 1 diff --git a/algorithms/string/reverse_words.py b/algorithms/string/reverse_words.py index ceb80d62d..f080e8e4d 100644 --- a/algorithms/string/reverse_words.py +++ b/algorithms/string/reverse_words.py @@ -1,5 +1,4 @@ -""" -Reverse Words in a String +"""Reverse Words in a String. Given a string, reverse the order of words. Leading and trailing spaces are trimmed and words are separated by single spaces. @@ -21,6 +20,7 @@ def _reverse_list(array: list[str], left: int, right: int) -> None: array: The list to modify. left: The starting index. right: The ending index. + """ while left < right: array[left], array[right] = array[right], array[left] @@ -40,6 +40,7 @@ def reverse_words(string: str) -> str: Examples: >>> reverse_words("I am keon kim and I like pizza") 'pizza like I and kim keon am I' + """ words = string.strip().split() word_count = len(words) diff --git a/algorithms/string/roman_to_int.py b/algorithms/string/roman_to_int.py index 9f91c7e39..baa5574f4 100644 --- a/algorithms/string/roman_to_int.py +++ b/algorithms/string/roman_to_int.py @@ -1,5 +1,4 @@ -""" -Roman Numeral to Integer +"""Roman Numeral to Integer. Given a Roman numeral string, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999. @@ -32,6 +31,7 @@ def roman_to_int(text: str) -> int: 1994 >>> roman_to_int("") 0 + """ if not text: return 0 diff --git a/algorithms/string/rotate.py b/algorithms/string/rotate.py index 9beb493fa..71a539837 100644 --- a/algorithms/string/rotate.py +++ b/algorithms/string/rotate.py @@ -1,5 +1,4 @@ -""" -Rotate String +"""Rotate String. Given a string and an integer k, return the string rotated by k positions to the left. Two approaches are provided. @@ -27,12 +26,12 @@ def rotate(text: str, positions: int) -> str: Examples: >>> rotate("hello", 2) 'llohe' + """ long_string = text * (positions // len(text) + 2) if positions <= len(text): return long_string[positions : positions + len(text)] - else: - return long_string[positions - len(text) : positions] + return long_string[positions - len(text) : positions] def rotate_alt(string: str, positions: int) -> str: @@ -48,6 +47,7 @@ def rotate_alt(string: str, positions: int) -> str: Examples: >>> rotate_alt("hello", 2) 'llohe' + """ positions = positions % len(string) return string[positions:] + string[:positions] diff --git a/algorithms/string/strip_url_params.py b/algorithms/string/strip_url_params.py index 0859f05eb..0ca645e6a 100644 --- a/algorithms/string/strip_url_params.py +++ b/algorithms/string/strip_url_params.py @@ -1,5 +1,4 @@ -""" -Strip URL Parameters +"""Strip URL Parameters. Remove duplicate query string parameters from a URL and optionally remove specified parameters. Three approaches of increasing Pythonic style. @@ -31,6 +30,7 @@ def strip_url_params1(url: str, params_to_strip: list[str] | None = None) -> str Examples: >>> strip_url_params1("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' + """ if not params_to_strip: params_to_strip = [] @@ -44,50 +44,47 @@ def strip_url_params1(url: str, params_to_strip: list[str] | None = None) -> str result += "?" if not query_string: return url - else: - key_value_pairs: list[str] = [] - fragment = "" - for char in query_string: - if char.isdigit(): - key_value_pairs.append(fragment + char) - fragment = "" + key_value_pairs: list[str] = [] + fragment = "" + for char in query_string: + if char.isdigit(): + key_value_pairs.append(fragment + char) + fragment = "" + else: + fragment += char + seen: dict[str, int] = defaultdict(int) + for pair in key_value_pairs: + token_parts = pair.split("=") + if token_parts[0]: + length = len(token_parts[0]) + if length == 1: + if token_parts and (token_parts[0] not in seen): + if params_to_strip: + if token_parts[0] != params_to_strip[0]: + seen[token_parts[0]] = token_parts[1] + result = ( + result + token_parts[0] + "=" + token_parts[1] + ) + elif token_parts[0] not in seen: + seen[token_parts[0]] = token_parts[1] + result = ( + result + token_parts[0] + "=" + token_parts[1] + ) else: - fragment += char - seen: dict[str, int] = defaultdict(int) - for pair in key_value_pairs: - token_parts = pair.split("=") - if token_parts[0]: - length = len(token_parts[0]) - if length == 1: - if token_parts and (token_parts[0] not in seen): - if params_to_strip: - if token_parts[0] != params_to_strip[0]: - seen[token_parts[0]] = token_parts[1] - result = ( - result + token_parts[0] + "=" + token_parts[1] - ) - else: - if token_parts[0] not in seen: - seen[token_parts[0]] = token_parts[1] - result = ( - result + token_parts[0] + "=" + token_parts[1] - ) - else: - check = token_parts[0] - letter = check[1] - if token_parts and (letter not in seen): - if params_to_strip: - if letter != params_to_strip[0]: - seen[letter] = token_parts[1] - result = ( - result + token_parts[0] + "=" + token_parts[1] - ) - else: - if letter not in seen: - seen[letter] = token_parts[1] - result = ( - result + token_parts[0] + "=" + token_parts[1] - ) + check = token_parts[0] + letter = check[1] + if token_parts and (letter not in seen): + if params_to_strip: + if letter != params_to_strip[0]: + seen[letter] = token_parts[1] + result = ( + result + token_parts[0] + "=" + token_parts[1] + ) + elif letter not in seen: + seen[letter] = token_parts[1] + result = ( + result + token_parts[0] + "=" + token_parts[1] + ) return result @@ -104,6 +101,7 @@ def strip_url_params2(url: str, param_to_strip: list[str] | None = None) -> str: Examples: >>> strip_url_params2("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' + """ if param_to_strip is None: param_to_strip = [] @@ -119,7 +117,7 @@ def strip_url_params2(url: str, param_to_strip: list[str] | None = None) -> str: ): queries.pop(index) - return url.split("?")[0] + "?" + "&".join(queries) + return url.split("?", maxsplit=1)[0] + "?" + "&".join(queries) def strip_url_params3(url: str, strip: list[str] | None = None) -> str: @@ -135,6 +133,7 @@ def strip_url_params3(url: str, strip: list[str] | None = None) -> str: Examples: >>> strip_url_params3("www.saadbenn.com?a=1&b=2&a=2") 'www.saadbenn.com?a=1&b=2' + """ if not strip: strip = [] diff --git a/algorithms/string/strong_password.py b/algorithms/string/strong_password.py index 6c5e73d62..4a3ee325d 100644 --- a/algorithms/string/strong_password.py +++ b/algorithms/string/strong_password.py @@ -1,5 +1,4 @@ -""" -Strong Password Checker +"""Strong Password Checker. Given a password string, determine the minimum number of characters that must be added to make it strong. A strong password has at least 6 characters, @@ -28,6 +27,7 @@ def strong_password(length: int, password: str) -> int: Examples: >>> strong_password(3, "Ab1") 3 + """ missing_types = 0 if not any(char.isdigit() for char in password): diff --git a/algorithms/string/text_justification.py b/algorithms/string/text_justification.py index 5bec67734..8e09c9fdf 100644 --- a/algorithms/string/text_justification.py +++ b/algorithms/string/text_justification.py @@ -1,5 +1,4 @@ -""" -Text Justification +"""Text Justification. Given an array of words and a max width, format the text such that each line has exactly max_width characters and is fully justified. Extra spaces are @@ -31,6 +30,7 @@ def text_justification(words: list[str], max_width: int) -> list[str]: Examples: >>> text_justification(["What", "must", "be"], 16) ['What must be '] + """ result: list[str] = [] row_length = 0 @@ -41,8 +41,9 @@ def text_justification(words: list[str], max_width: int) -> list[str]: while index < len(words): while row_length <= max_width and index < len(words): if len(words[index]) > max_width: + msg = "there exists word whose length is larger than max_width" raise ValueError( - "there exists word whose length is larger than max_width" + msg, ) tentative_length = row_length row_words.append(words[index]) diff --git a/algorithms/string/unique_morse.py b/algorithms/string/unique_morse.py index 71b401300..224119c51 100644 --- a/algorithms/string/unique_morse.py +++ b/algorithms/string/unique_morse.py @@ -1,5 +1,4 @@ -""" -Unique Morse Code Representations +"""Unique Morse Code Representations. Given a list of words, determine the number of unique Morse code transformations among all the words. @@ -55,6 +54,7 @@ def convert_morse_word(word: str) -> str: Examples: >>> convert_morse_word("gin") '--...-.' + """ morse_word = "" word = word.lower() @@ -75,6 +75,7 @@ def unique_morse(words: list[str]) -> int: Examples: >>> unique_morse(["gin", "zen", "gig", "msg"]) 2 + """ unique_transformations: list[str] = [] for word in words: diff --git a/algorithms/string/validate_coordinates.py b/algorithms/string/validate_coordinates.py index 5553450e4..6bd46ab40 100644 --- a/algorithms/string/validate_coordinates.py +++ b/algorithms/string/validate_coordinates.py @@ -1,5 +1,4 @@ -""" -Validate Coordinates +"""Validate Coordinates. Validate if given parameters are valid geographical coordinates. Latitude must be between -90 and 90, longitude between -180 and 180. @@ -28,6 +27,7 @@ def is_valid_coordinates_0(coordinates: str) -> bool: Examples: >>> is_valid_coordinates_0("-23, 25") True + """ for char in coordinates: if not (char.isdigit() or char in ["-", ".", ",", " "]): @@ -55,6 +55,7 @@ def is_valid_coordinates_1(coordinates: str) -> bool: Examples: >>> is_valid_coordinates_1("43.91343345, 143") True + """ try: latitude, longitude = [ @@ -77,10 +78,11 @@ def is_valid_coordinates_regular_expression(coordinates: str) -> bool: Examples: >>> is_valid_coordinates_regular_expression("4, -3") True + """ return bool( re.match( r"-?(\d|[1-8]\d|90)\.?\d*, -?(\d|[1-9]\d|1[0-7]\d|180)\.?\d*$", coordinates, - ) + ), ) diff --git a/algorithms/string/word_squares.py b/algorithms/string/word_squares.py index 730ca9ba9..8b2a1c7d1 100644 --- a/algorithms/string/word_squares.py +++ b/algorithms/string/word_squares.py @@ -1,5 +1,4 @@ -""" -Word Squares +"""Word Squares. Given a set of words (without duplicates), find all word squares that can be built from them. A word square reads the same horizontally and vertically. @@ -28,6 +27,7 @@ def word_squares(words: list[str]) -> list[list[str]]: Examples: >>> word_squares(["area", "lead", "wall", "lady", "ball"]) [['wall', 'area', 'lead', 'lady'], ['ball', 'area', 'lead', 'lady']] + """ word_length = len(words[0]) prefix_map: dict[str, list[str]] = collections.defaultdict(list) @@ -43,7 +43,7 @@ def _build(square: list[str]) -> None: for row in range(len(square)): prefix += square[row][len(square)] for word in prefix_map[prefix]: - _build(square + [word]) + _build([*square, word]) squares: list[list[str]] = [] for word in words: diff --git a/algorithms/tree/__init__.py b/algorithms/tree/__init__.py index 6e6126c30..a5d5b7ce5 100644 --- a/algorithms/tree/__init__.py +++ b/algorithms/tree/__init__.py @@ -1,5 +1,4 @@ -""" -Tree Algorithms +"""Tree Algorithms. A collection of binary tree and general tree algorithms including traversal, search, construction, and property-checking operations. @@ -43,20 +42,22 @@ "TreeNode", "bin_tree_to_list", "binary_tree_paths", + "bottom_view", "construct_tree", "construct_tree_util", "find_deepest_left", "has_path_sum", "has_path_sum2", "has_path_sum3", + "inorder", + "inorder_rec", "is_balanced", "is_same_tree", "is_subtree", "is_symmetric", "is_symmetric_iterative", - "inorder", - "inorder_rec", "lca", + "left_view", "longest_consecutive", "max_height", "max_path_sum", @@ -66,9 +67,7 @@ "path_sum2", "path_sum3", "reverse", - "tree_print", - "left_view", "right_view", "top_view", - "bottom_view", + "tree_print", ] diff --git a/algorithms/tree/bin_tree_to_list.py b/algorithms/tree/bin_tree_to_list.py index 0d596cf4f..52e9bd3e6 100644 --- a/algorithms/tree/bin_tree_to_list.py +++ b/algorithms/tree/bin_tree_to_list.py @@ -1,5 +1,4 @@ -""" -Binary Tree to Doubly Linked List +"""Binary Tree to Doubly Linked List. Converts a binary tree to a sorted doubly linked list in-place by rearranging the left and right pointers of each node. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def bin_tree_to_list(root: TreeNode | None) -> TreeNode | None: @@ -29,6 +31,7 @@ def bin_tree_to_list(root: TreeNode | None) -> TreeNode | None: Examples: >>> bin_tree_to_list(None) is None True + """ if not root: return root @@ -46,6 +49,7 @@ def _bin_tree_to_list_util(root: TreeNode | None) -> TreeNode | None: Returns: The root of the partially converted subtree. + """ if not root: return root diff --git a/algorithms/tree/binary_tree_paths.py b/algorithms/tree/binary_tree_paths.py index a31a39351..e12a492fb 100644 --- a/algorithms/tree/binary_tree_paths.py +++ b/algorithms/tree/binary_tree_paths.py @@ -1,5 +1,4 @@ -""" -Binary Tree Paths +"""Binary Tree Paths. Given a binary tree, return all root-to-leaf paths as a list of strings in the format "root->...->leaf". @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def binary_tree_paths(root: TreeNode | None) -> list[str]: @@ -28,6 +30,7 @@ def binary_tree_paths(root: TreeNode | None) -> list[str]: Examples: >>> binary_tree_paths(None) [] + """ result: list[str] = [] if root is None: @@ -43,6 +46,7 @@ def _dfs(result: list[str], root: TreeNode, current: str) -> None: result: The list accumulating path strings. root: The current node being visited. current: The path string built so far. + """ if root.left is None and root.right is None: result.append(current) diff --git a/algorithms/tree/binary_tree_views.py b/algorithms/tree/binary_tree_views.py index 91b724acb..067b482e5 100644 --- a/algorithms/tree/binary_tree_views.py +++ b/algorithms/tree/binary_tree_views.py @@ -1,5 +1,4 @@ -""" -Binary Tree Views +"""Binary Tree Views. Compute different "views" of a binary tree — the nodes visible when looking at the tree from a particular direction. @@ -20,8 +19,10 @@ from __future__ import annotations from collections import deque +from typing import TYPE_CHECKING -from algorithms.common.tree_node import TreeNode +if TYPE_CHECKING: + from algorithms.common.tree_node import TreeNode def left_view(root: TreeNode | None) -> list[int]: @@ -38,6 +39,7 @@ def left_view(root: TreeNode | None) -> list[int]: >>> root = TreeNode(1, TreeNode(2, TreeNode(4)), TreeNode(3)) >>> left_view(root) [1, 2, 4] + """ if root is None: return [] @@ -70,6 +72,7 @@ def right_view(root: TreeNode | None) -> list[int]: >>> root = TreeNode(1, TreeNode(2, TreeNode(4)), TreeNode(3)) >>> right_view(root) [1, 3, 4] + """ if root is None: return [] @@ -105,6 +108,7 @@ def top_view(root: TreeNode | None) -> list[int]: ... TreeNode(3, None, TreeNode(6))) >>> top_view(root) [4, 2, 1, 3, 6] + """ if root is None: return [] @@ -141,6 +145,7 @@ def bottom_view(root: TreeNode | None) -> list[int]: ... TreeNode(3, None, TreeNode(6))) >>> bottom_view(root) [4, 2, 5, 3, 6] + """ if root is None: return [] diff --git a/algorithms/tree/bst_array_to_bst.py b/algorithms/tree/bst_array_to_bst.py index dfe3f2152..5178309c1 100644 --- a/algorithms/tree/bst_array_to_bst.py +++ b/algorithms/tree/bst_array_to_bst.py @@ -1,5 +1,4 @@ -""" -Given an array where elements are sorted in ascending order, +"""Given an array where elements are sorted in ascending order, convert it to a height balanced BST. """ diff --git a/algorithms/tree/bst_closest_value.py b/algorithms/tree/bst_closest_value.py index 934d69025..41dee6fac 100644 --- a/algorithms/tree/bst_closest_value.py +++ b/algorithms/tree/bst_closest_value.py @@ -7,17 +7,8 @@ # that is closest to the target. -# Definition for a binary tree node. -# class TreeNode(object): -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - - def closest_value(root, target): - """ - :type root: TreeNode + """:type root: TreeNode :type target: float :rtype: int """ diff --git a/algorithms/tree/bst_count_left_node.py b/algorithms/tree/bst_count_left_node.py index 95302e5d9..5428164cc 100644 --- a/algorithms/tree/bst_count_left_node.py +++ b/algorithms/tree/bst_count_left_node.py @@ -1,5 +1,4 @@ -""" -Write a function count_left_node returns the number of left children in the +r"""Write a function count_left_node returns the number of left children in the tree. For example: the following tree has four left children (the nodes storing the values 6, 3, 7, and 10): @@ -15,52 +14,10 @@ """ -import unittest - -from algorithms.data_structures.bst import BST - def count_left_node(root): if root is None: return 0 - elif root.left is None: + if root.left is None: return count_left_node(root.right) - else: - return 1 + count_left_node(root.left) + count_left_node(root.right) - - -""" - The tree is created for testing: - - 9 - / \ - 6 12 - / \\ / \ - 3 8 10 15 - / \ - 7 18 - - count_left_node = 4 - -""" - - -class TestSuite(unittest.TestCase): - def setUp(self): - self.tree = BST() - self.tree.insert(9) - self.tree.insert(6) - self.tree.insert(12) - self.tree.insert(3) - self.tree.insert(8) - self.tree.insert(10) - self.tree.insert(15) - self.tree.insert(7) - self.tree.insert(18) - - def test_count_left_node(self): - self.assertEqual(4, count_left_node(self.tree.root)) - - -if __name__ == "__main__": - unittest.main() + return 1 + count_left_node(root.left) + count_left_node(root.right) diff --git a/algorithms/tree/bst_delete_node.py b/algorithms/tree/bst_delete_node.py index f72b0c462..6e34e0d30 100644 --- a/algorithms/tree/bst_delete_node.py +++ b/algorithms/tree/bst_delete_node.py @@ -1,5 +1,4 @@ -""" -Given a root node reference of a BST and a key, delete the node +r"""Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST. @@ -10,7 +9,6 @@ Note: Time complexity should be O(height of tree). Example: - root = [5,3,6,2,4,null,7] key = 3 @@ -37,13 +35,13 @@ 2 6 \\ \ 4 7 + """ class Solution: def delete_node(self, root, key): - """ - :type root: TreeNode + """:type root: TreeNode :type key: int :rtype: TreeNode """ @@ -60,11 +58,10 @@ def delete_node(self, root, key): left_right_most.right = root.right # Return left child instead of root, a.k.a delete root return root.left - else: - return root.right + return root.right # If left or right child got deleted, the returned root is # the child of the deleted node. - elif root.val > key: + if root.val > key: root.left = self.delete_node(root.left, key) else: root.right = self.delete_node(root.right, key) diff --git a/algorithms/tree/bst_depth_sum.py b/algorithms/tree/bst_depth_sum.py index b44602c20..a50f65460 100644 --- a/algorithms/tree/bst_depth_sum.py +++ b/algorithms/tree/bst_depth_sum.py @@ -1,5 +1,4 @@ -""" -Write a function depthSum returns the sum of the values stored +r"""Write a function depthSum returns the sum of the values stored in a binary search tree of integers weighted by the depth of each value. For example: @@ -16,61 +15,20 @@ """ -import unittest - -from algorithms.data_structures.bst import BST - def depth_sum(root, n): if root: return recur_depth_sum(root, 1) + return None def recur_depth_sum(root, n): if root is None: return 0 - elif root.left is None and root.right is None: + if root.left is None and root.right is None: return root.data * n - else: - return ( - n * root.data - + recur_depth_sum(root.left, n + 1) - + recur_depth_sum(root.right, n + 1) - ) - - -""" - The tree is created for testing: - - 9 - / \ - 6 12 - / \\ / \ - 3 8 10 15 - / \ - 7 18 - - depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18) - -""" - - -class TestSuite(unittest.TestCase): - def setUp(self): - self.tree = BST() - self.tree.insert(9) - self.tree.insert(6) - self.tree.insert(12) - self.tree.insert(3) - self.tree.insert(8) - self.tree.insert(10) - self.tree.insert(15) - self.tree.insert(7) - self.tree.insert(18) - - def test_depth_sum(self): - self.assertEqual(253, depth_sum(self.tree.root, 4)) - - -if __name__ == "__main__": - unittest.main() + return ( + n * root.data + + recur_depth_sum(root.left, n + 1) + + recur_depth_sum(root.right, n + 1) + ) diff --git a/algorithms/tree/bst_height.py b/algorithms/tree/bst_height.py index 8d4adaa4f..3ca6b823f 100644 --- a/algorithms/tree/bst_height.py +++ b/algorithms/tree/bst_height.py @@ -1,8 +1,7 @@ -""" -Write a function height returns the height of a tree. The height is defined to +r"""Write a function height returns the height of a tree. The height is defined to be the number of levels. The empty tree has height 0, a tree of one node has height 1, a root node with one or two leaves as children has height 2, and so on -For example: height of tree is 4 +For example: height of tree is 4. 9 / \ @@ -16,50 +15,8 @@ """ -import unittest - -from algorithms.data_structures.bst import BST - def height(root): if root is None: return 0 - else: - return 1 + max(height(root.left), height(root.right)) - - -""" - The tree is created for testing: - - 9 - / \ - 6 12 - / \\ / \ - 3 8 10 15 - / \ - 7 18 - - count_left_node = 4 - -""" - - -class TestSuite(unittest.TestCase): - def setUp(self): - self.tree = BST() - self.tree.insert(9) - self.tree.insert(6) - self.tree.insert(12) - self.tree.insert(3) - self.tree.insert(8) - self.tree.insert(10) - self.tree.insert(15) - self.tree.insert(7) - self.tree.insert(18) - - def test_height(self): - self.assertEqual(4, height(self.tree.root)) - - -if __name__ == "__main__": - unittest.main() + return 1 + max(height(root.left), height(root.right)) diff --git a/algorithms/tree/bst_is_bst.py b/algorithms/tree/bst_is_bst.py index f3efefde8..a05b99a8e 100644 --- a/algorithms/tree/bst_is_bst.py +++ b/algorithms/tree/bst_is_bst.py @@ -1,5 +1,4 @@ -""" -Given a binary tree, determine if it is a valid binary search tree (BST). +"""Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defined as follows: @@ -21,12 +20,10 @@ """ -def is_bst(root): - """ - :type root: TreeNode +def is_bst(root) -> bool: + """:type root: TreeNode :rtype: bool """ - stack = [] pre = None diff --git a/algorithms/tree/bst_iterator.py b/algorithms/tree/bst_iterator.py index 90f741b5a..64c9d6841 100644 --- a/algorithms/tree/bst_iterator.py +++ b/algorithms/tree/bst_iterator.py @@ -1,5 +1,5 @@ class BSTIterator: - def __init__(self, root): + def __init__(self, root) -> None: self.stack = [] while root: self.stack.append(root) diff --git a/algorithms/tree/bst_kth_smallest.py b/algorithms/tree/bst_kth_smallest.py index d56fab450..d1c728f0e 100644 --- a/algorithms/tree/bst_kth_smallest.py +++ b/algorithms/tree/bst_kth_smallest.py @@ -1,5 +1,5 @@ class Node: - def __init__(self, val, left=None, right=None): + def __init__(self, val, left=None, right=None) -> None: self.val = val self.left = left self.right = right @@ -21,8 +21,7 @@ def kth_smallest(root, k): class Solution: def kth_smallest(self, root, k): - """ - :type root: TreeNode + """:type root: TreeNode :type k: int :rtype: int """ @@ -30,25 +29,10 @@ def kth_smallest(self, root, k): self.helper(root, count) return count[k - 1] - def helper(self, node, count): + def helper(self, node, count) -> None: if not node: return self.helper(node.left, count) count.append(node.val) self.helper(node.right, count) - - -if __name__ == "__main__": - n1 = Node(100) - n2 = Node(50) - n3 = Node(150) - n4 = Node(25) - n5 = Node(75) - n6 = Node(125) - n7 = Node(175) - n1.left, n1.right = n2, n3 - n2.left, n2.right = n4, n5 - n3.left, n3.right = n6, n7 - print(kth_smallest(n1, 2)) - print(Solution().kth_smallest(n1, 2)) diff --git a/algorithms/tree/bst_lowest_common_ancestor.py b/algorithms/tree/bst_lowest_common_ancestor.py index 491359784..19da88d77 100644 --- a/algorithms/tree/bst_lowest_common_ancestor.py +++ b/algorithms/tree/bst_lowest_common_ancestor.py @@ -1,5 +1,4 @@ -""" -Given a binary search tree (BST), +r"""Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST. According to the definition of LCA on Wikipedia: @@ -22,8 +21,7 @@ def lowest_common_ancestor(root, p, q): - """ - :type root: Node + """:type root: Node :type p: Node :type q: Node :rtype: Node @@ -35,3 +33,4 @@ def lowest_common_ancestor(root, p, q): root = root.left else: return root + return None diff --git a/algorithms/tree/bst_num_empty.py b/algorithms/tree/bst_num_empty.py index 945531011..3dbbdda61 100644 --- a/algorithms/tree/bst_num_empty.py +++ b/algorithms/tree/bst_num_empty.py @@ -1,9 +1,8 @@ -""" -Write a function num_empty returns returns the number of empty branches in a +r"""Write a function num_empty returns returns the number of empty branches in a tree. Function should count the total number of empty branches among the nodes of the tree. A leaf node has two empty branches. In the case, if root is None, it considered as a 1 empty branch -For example: the following tree has 10 empty branch (* is empty branch) +For example: the following tree has 10 empty branch (* is empty branch). 9 __ / \\___ @@ -19,54 +18,12 @@ """ -import unittest - -from algorithms.data_structures.bst import BST - def num_empty(root): if root is None: return 1 - elif root.left is None and root.right: + if root.left is None and root.right: return 1 + num_empty(root.right) - elif root.right is None and root.left: + if root.right is None and root.left: return 1 + num_empty(root.left) - else: - return num_empty(root.left) + num_empty(root.right) - - -""" - The tree is created for testing: - - 9 - / \ - 6 12 - / \\ / \ - 3 8 10 15 - / \ - 7 18 - - num_empty = 10 - -""" - - -class TestSuite(unittest.TestCase): - def setUp(self): - self.tree = BST() - self.tree.insert(9) - self.tree.insert(6) - self.tree.insert(12) - self.tree.insert(3) - self.tree.insert(8) - self.tree.insert(10) - self.tree.insert(15) - self.tree.insert(7) - self.tree.insert(18) - - def test_num_empty(self): - self.assertEqual(10, num_empty(self.tree.root)) - - -if __name__ == "__main__": - unittest.main() + return num_empty(root.left) + num_empty(root.right) diff --git a/algorithms/tree/bst_serialize_deserialize.py b/algorithms/tree/bst_serialize_deserialize.py index 285d19cb3..212289c3d 100644 --- a/algorithms/tree/bst_serialize_deserialize.py +++ b/algorithms/tree/bst_serialize_deserialize.py @@ -2,7 +2,7 @@ def serialize(root): - def build_string(node): + def build_string(node) -> None: if node: vals.append(str(node.val)) build_string(node.left) diff --git a/algorithms/tree/bst_unique_bst.py b/algorithms/tree/bst_unique_bst.py index 53bf22790..eae561e92 100644 --- a/algorithms/tree/bst_unique_bst.py +++ b/algorithms/tree/bst_unique_bst.py @@ -1,5 +1,4 @@ -""" -Given n, how many structurally unique BST's +r"""Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For example, @@ -28,8 +27,7 @@ def num_trees(n): - """ - :type n: int + """:type n: int :rtype: int """ dp = [0] * (n + 1) diff --git a/algorithms/tree/bst_validate_bst.py b/algorithms/tree/bst_validate_bst.py index b0071a38b..f9ed7050e 100644 --- a/algorithms/tree/bst_validate_bst.py +++ b/algorithms/tree/bst_validate_bst.py @@ -1,7 +1,6 @@ # =============================================================================== # Validate Binary Search Tree -""" -To check if the given binary tree is a valid binary search +"""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. @@ -17,16 +16,14 @@ # 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). + """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) @@ -65,25 +62,3 @@ def validate_bst(node): 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}") diff --git a/algorithms/tree/construct_tree_postorder_preorder.py b/algorithms/tree/construct_tree_postorder_preorder.py index 79cbb0e31..bf8b8efea 100644 --- a/algorithms/tree/construct_tree_postorder_preorder.py +++ b/algorithms/tree/construct_tree_postorder_preorder.py @@ -1,5 +1,4 @@ -""" -Construct Tree from Preorder and Postorder Traversal +"""Construct Tree from Preorder and Postorder Traversal. Given preorder and postorder traversals of a full binary tree, construct the tree and return its inorder traversal. A full binary tree has either zero or @@ -20,7 +19,7 @@ def construct_tree_util( - pre: list[int], post: list[int], low: int, high: int, size: int + pre: list[int], post: list[int], low: int, high: int, size: int, ) -> TreeNode | None: """Recursively construct a binary tree from preorder and postorder arrays. @@ -40,6 +39,7 @@ def construct_tree_util( Examples: >>> construct_tree_util([1, 2, 3], [2, 3, 1], 0, 2, 3) is not None True + """ global pre_index @@ -83,6 +83,7 @@ def construct_tree(pre: list[int], post: list[int], size: int) -> list[int]: Examples: >>> construct_tree([1, 2, 4, 5, 3, 6, 7], [4, 5, 2, 6, 7, 3, 1], 7) [4, 2, 5, 1, 6, 3, 7] + """ root = construct_tree_util(pre, post, 0, size - 1, size) return _inorder(root) @@ -97,6 +98,7 @@ def _inorder(root: TreeNode | None, result: list[int] | None = None) -> list[int Returns: A list of node values in inorder sequence. + """ if root is None: return [] diff --git a/algorithms/tree/deepest_left.py b/algorithms/tree/deepest_left.py index 6888c499e..c0813ec5c 100644 --- a/algorithms/tree/deepest_left.py +++ b/algorithms/tree/deepest_left.py @@ -1,5 +1,4 @@ -""" -Deepest Left Leaf +"""Deepest Left Leaf. Given a binary tree, find the deepest node that is the left child of its parent node. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode class DeepestLeft: @@ -23,6 +25,7 @@ class DeepestLeft: >>> dl = DeepestLeft() >>> dl.depth 0 + """ def __init__(self) -> None: @@ -31,7 +34,7 @@ def __init__(self) -> None: def find_deepest_left( - root: TreeNode | None, is_left: bool, depth: int, res: DeepestLeft + root: TreeNode | None, is_left: bool, depth: int, res: DeepestLeft, ) -> None: """Recursively find the deepest left child in a binary tree. @@ -44,6 +47,7 @@ def find_deepest_left( Examples: >>> res = DeepestLeft() >>> find_deepest_left(None, True, 1, res) + """ if not root: return diff --git a/algorithms/tree/invert_tree.py b/algorithms/tree/invert_tree.py index 99f2e25d0..d742b5f77 100644 --- a/algorithms/tree/invert_tree.py +++ b/algorithms/tree/invert_tree.py @@ -1,5 +1,4 @@ -""" -Invert Binary Tree +"""Invert Binary Tree. Inverts a binary tree by swapping the left and right children of every node. @@ -12,7 +11,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def reverse(root: TreeNode | None) -> None: @@ -23,6 +25,7 @@ def reverse(root: TreeNode | None) -> None: Examples: >>> reverse(None) + """ if root is None: return diff --git a/algorithms/tree/is_balanced.py b/algorithms/tree/is_balanced.py index d50bd95a9..8515818d0 100644 --- a/algorithms/tree/is_balanced.py +++ b/algorithms/tree/is_balanced.py @@ -1,5 +1,4 @@ -""" -Balanced Binary Tree +"""Balanced Binary Tree. Determines whether a binary tree is height-balanced, meaning the depth of the left and right subtrees of every node differ by at most one. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def is_balanced(root: TreeNode | None) -> bool: @@ -28,6 +30,7 @@ def is_balanced(root: TreeNode | None) -> bool: Examples: >>> is_balanced(None) True + """ return _get_depth(root) != -1 @@ -40,6 +43,7 @@ def _get_depth(root: TreeNode | None) -> int: Returns: The depth of the subtree, or -1 if it is unbalanced. + """ if root is None: return 0 diff --git a/algorithms/tree/is_subtree.py b/algorithms/tree/is_subtree.py index 24e52fc10..801d0568b 100644 --- a/algorithms/tree/is_subtree.py +++ b/algorithms/tree/is_subtree.py @@ -1,5 +1,4 @@ -""" -Subtree Check +"""Subtree Check. Given two binary trees s and t, check whether t is a subtree of s. A subtree of s is a tree consisting of a node in s and all of its descendants. @@ -14,8 +13,10 @@ from __future__ import annotations import collections +from typing import TYPE_CHECKING -from algorithms.tree.tree import TreeNode +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def is_subtree(big: TreeNode, small: TreeNode) -> bool: @@ -32,6 +33,7 @@ def is_subtree(big: TreeNode, small: TreeNode) -> bool: >>> node = TreeNode(1) >>> is_subtree(node, node) True + """ flag = False queue: collections.deque[TreeNode] = collections.deque() @@ -41,9 +43,8 @@ def is_subtree(big: TreeNode, small: TreeNode) -> bool: if node.val == small.val: flag = _comp(node, small) break - else: - queue.append(node.left) - queue.append(node.right) + queue.append(node.left) + queue.append(node.right) return flag @@ -56,6 +57,7 @@ def _comp(p: TreeNode | None, q: TreeNode | None) -> bool: Returns: True if both subtrees are identical, False otherwise. + """ if p is None and q is None: return True diff --git a/algorithms/tree/is_symmetric.py b/algorithms/tree/is_symmetric.py index f53fd5027..d39f380e9 100644 --- a/algorithms/tree/is_symmetric.py +++ b/algorithms/tree/is_symmetric.py @@ -1,5 +1,4 @@ -""" -Symmetric Tree +"""Symmetric Tree. Given a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center). Provides both recursive and iterative solutions. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def is_symmetric(root: TreeNode | None) -> bool: @@ -28,6 +30,7 @@ def is_symmetric(root: TreeNode | None) -> bool: Examples: >>> is_symmetric(None) True + """ if root is None: return True @@ -43,6 +46,7 @@ def _helper(p: TreeNode | None, q: TreeNode | None) -> bool: Returns: True if the subtrees are mirror images, False otherwise. + """ if p is None and q is None: return True @@ -63,6 +67,7 @@ def is_symmetric_iterative(root: TreeNode | None) -> bool: Examples: >>> is_symmetric_iterative(None) True + """ if root is None: return True diff --git a/algorithms/tree/longest_consecutive.py b/algorithms/tree/longest_consecutive.py index e98de93c3..434bc3016 100644 --- a/algorithms/tree/longest_consecutive.py +++ b/algorithms/tree/longest_consecutive.py @@ -1,5 +1,4 @@ -""" -Longest Consecutive Sequence in Binary Tree +"""Longest Consecutive Sequence in Binary Tree. Given a binary tree, find the length of the longest consecutive sequence path from parent to child (values increasing by one). @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def longest_consecutive(root: TreeNode | None) -> int: @@ -28,6 +30,7 @@ def longest_consecutive(root: TreeNode | None) -> int: Examples: >>> longest_consecutive(None) 0 + """ if root is None: return 0 @@ -44,6 +47,7 @@ def _dfs(root: TreeNode | None, current: int, target: int, max_len: int) -> None current: The current consecutive sequence length. target: The expected value for the current node to continue the sequence. max_len: The maximum sequence length found so far. + """ if root is None: return diff --git a/algorithms/tree/lowest_common_ancestor.py b/algorithms/tree/lowest_common_ancestor.py index cdc8352e2..692e81ff0 100644 --- a/algorithms/tree/lowest_common_ancestor.py +++ b/algorithms/tree/lowest_common_ancestor.py @@ -1,5 +1,4 @@ -""" -Lowest Common Ancestor +"""Lowest Common Ancestor. Given a binary tree, find the lowest common ancestor (LCA) of two given nodes. The LCA is the lowest node that has both nodes as descendants (a node can be a @@ -14,7 +13,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def lca(root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None: @@ -32,6 +34,7 @@ def lca(root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None: >>> node = TreeNode(1) >>> lca(node, node, node).val 1 + """ if root is None or root is p or root is q: return root @@ -39,4 +42,4 @@ def lca(root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None: right = lca(root.right, p, q) if left is not None and right is not None: return root - return left if left else right + return left or right diff --git a/algorithms/tree/max_height.py b/algorithms/tree/max_height.py index 23b805739..0c12be2b0 100644 --- a/algorithms/tree/max_height.py +++ b/algorithms/tree/max_height.py @@ -1,5 +1,4 @@ -""" -Maximum Depth of Binary Tree +"""Maximum Depth of Binary Tree. Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the longest path from the root down to the farthest leaf. @@ -14,8 +13,10 @@ from __future__ import annotations from collections import deque +from typing import TYPE_CHECKING -from algorithms.tree.tree import TreeNode +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def max_height(root: TreeNode | None) -> int: @@ -30,6 +31,7 @@ def max_height(root: TreeNode | None) -> int: Examples: >>> max_height(None) 0 + """ if root is None: return 0 diff --git a/algorithms/tree/max_path_sum.py b/algorithms/tree/max_path_sum.py index 677e442ea..b4ae4f5e4 100644 --- a/algorithms/tree/max_path_sum.py +++ b/algorithms/tree/max_path_sum.py @@ -1,5 +1,4 @@ -""" -Binary Tree Maximum Path Sum +"""Binary Tree Maximum Path Sum. Given a binary tree, find the maximum path sum. A path is any sequence of nodes from some starting node to any node in the tree along parent-child connections. @@ -14,7 +13,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def max_path_sum(root: TreeNode | None) -> float: @@ -29,6 +31,7 @@ def max_path_sum(root: TreeNode | None) -> float: Examples: >>> max_path_sum(TreeNode(1)) 1 + """ _, maximum = _helper(root) return maximum @@ -39,9 +42,11 @@ def _helper(root: TreeNode | None) -> tuple[float, float]: Args: root: The current node. + Returns: A tuple containing the best downward branch and the best complete path found in the subtree. + """ if root is None: return 0, float("-inf") diff --git a/algorithms/tree/min_height.py b/algorithms/tree/min_height.py index 48649f4c2..c066cbab7 100644 --- a/algorithms/tree/min_height.py +++ b/algorithms/tree/min_height.py @@ -1,5 +1,4 @@ -""" -Minimum Depth of Binary Tree +"""Minimum Depth of Binary Tree. Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root down to the nearest leaf. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def min_depth(self: object, root: TreeNode | None) -> int: @@ -25,6 +27,7 @@ def min_depth(self: object, root: TreeNode | None) -> int: Returns: The minimum depth of the tree. + """ if root is None: return 0 @@ -45,6 +48,7 @@ def min_height(root: TreeNode | None) -> int: Examples: >>> min_height(None) 0 + """ if root is None: return 0 diff --git a/algorithms/tree/path_sum.py b/algorithms/tree/path_sum.py index 5d5dce4cf..b3fb02be3 100644 --- a/algorithms/tree/path_sum.py +++ b/algorithms/tree/path_sum.py @@ -1,5 +1,4 @@ -""" -Path Sum +"""Path Sum. Given a binary tree and a target sum, determine if the tree has a root-to-leaf path such that adding up all values along the path equals the given sum. @@ -15,16 +14,18 @@ from __future__ import annotations from collections import deque +from typing import TYPE_CHECKING -from algorithms.tree.tree import TreeNode +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode -def has_path_sum(root: TreeNode | None, sum: int) -> bool: +def has_path_sum(root: TreeNode | None, target: int) -> bool: """Check if a root-to-leaf path with the given sum exists (recursive). Args: root: The root of the binary tree. - sum: The target sum. + target: The target sum. Returns: True if such a path exists, False otherwise. @@ -32,21 +33,22 @@ def has_path_sum(root: TreeNode | None, sum: int) -> bool: Examples: >>> has_path_sum(None, 0) False + """ if root is None: return False - if root.left is None and root.right is None and root.val == sum: + if root.left is None and root.right is None and root.val == target: return True - sum -= root.val - return has_path_sum(root.left, sum) or has_path_sum(root.right, sum) + target -= root.val + return has_path_sum(root.left, target) or has_path_sum(root.right, target) -def has_path_sum2(root: TreeNode | None, sum: int) -> bool: +def has_path_sum2(root: TreeNode | None, target: int) -> bool: """Check if a root-to-leaf path with the given sum exists (DFS with stack). Args: root: The root of the binary tree. - sum: The target sum. + target: The target sum. Returns: True if such a path exists, False otherwise. @@ -54,13 +56,14 @@ def has_path_sum2(root: TreeNode | None, sum: int) -> bool: Examples: >>> has_path_sum2(None, 0) False + """ if root is None: return False stack: list[tuple[TreeNode, int]] = [(root, root.val)] while stack: node, val = stack.pop() - if node.left is None and node.right is None and val == sum: + if node.left is None and node.right is None and val == target: return True if node.left is not None: stack.append((node.left, val + node.left.val)) @@ -69,12 +72,12 @@ def has_path_sum2(root: TreeNode | None, sum: int) -> bool: return False -def has_path_sum3(root: TreeNode | None, sum: int) -> bool: +def has_path_sum3(root: TreeNode | None, target: int) -> bool: """Check if a root-to-leaf path with the given sum exists (BFS with queue). Args: root: The root of the binary tree. - sum: The target sum. + target: The target sum. Returns: True if such a path exists, False otherwise. @@ -82,10 +85,11 @@ def has_path_sum3(root: TreeNode | None, sum: int) -> bool: Examples: >>> has_path_sum3(None, 0) False + """ if root is None: return False - queue: deque[tuple[TreeNode, int]] = deque([(root, sum - root.val)]) + queue: deque[tuple[TreeNode, int]] = deque([(root, target - root.val)]) while queue: node, val = queue.popleft() if node.left is None and node.right is None and val == 0: diff --git a/algorithms/tree/path_sum2.py b/algorithms/tree/path_sum2.py index b9dea4c63..59446da09 100644 --- a/algorithms/tree/path_sum2.py +++ b/algorithms/tree/path_sum2.py @@ -1,5 +1,4 @@ -""" -Path Sum II +"""Path Sum II. Given a binary tree and a target sum, find all root-to-leaf paths where each path's sum equals the given sum. Provides recursive, DFS, and BFS solutions. @@ -14,16 +13,18 @@ from __future__ import annotations from collections import deque +from typing import TYPE_CHECKING -from algorithms.tree.tree import TreeNode +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode -def path_sum(root: TreeNode | None, sum: int) -> list[list[int]]: +def path_sum(root: TreeNode | None, target: int) -> list[list[int]]: """Find all root-to-leaf paths with the given sum (recursive DFS). Args: root: The root of the binary tree. - sum: The target sum. + target: The target sum. Returns: A list of paths, where each path is a list of node values. @@ -31,30 +32,32 @@ def path_sum(root: TreeNode | None, sum: int) -> list[list[int]]: Examples: >>> path_sum(None, 0) [] + """ if root is None: return [] result: list[list[int]] = [] - _dfs(root, sum, [], result) + _dfs(root, target, [], result) return result -def _dfs(root: TreeNode, sum: int, path: list[int], result: list[list[int]]) -> None: +def _dfs(root: TreeNode, target: int, path: list[int], result: list[list[int]]) -> None: """Recursively collect paths that sum to the target value. Args: root: The current node. - sum: The remaining target sum. + target: The remaining target sum. path: The path accumulated so far. result: The list accumulating valid paths. + """ - if root.left is None and root.right is None and root.val == sum: + if root.left is None and root.right is None and root.val == target: path.append(root.val) result.append(path) if root.left is not None: - _dfs(root.left, sum - root.val, path + [root.val], result) + _dfs(root.left, target - root.val, [*path, root.val], result) if root.right is not None: - _dfs(root.right, sum - root.val, path + [root.val], result) + _dfs(root.right, target - root.val, [*path, root.val], result) def path_sum2(root: TreeNode | None, target: int) -> list[list[int]]: @@ -70,6 +73,7 @@ def path_sum2(root: TreeNode | None, target: int) -> list[list[int]]: Examples: >>> path_sum2(None, 0) [] + """ if root is None: return [] @@ -80,18 +84,18 @@ def path_sum2(root: TreeNode | None, target: int) -> list[list[int]]: if node.left is None and node.right is None and sum(path) == target: result.append(path) if node.left is not None: - stack.append((node.left, path + [node.left.val])) + stack.append((node.left, [*path, node.left.val])) if node.right is not None: - stack.append((node.right, path + [node.right.val])) + stack.append((node.right, [*path, node.right.val])) return result -def path_sum3(root: TreeNode | None, sum: int) -> list[list[int]]: +def path_sum3(root: TreeNode | None, target: int) -> list[list[int]]: """Find all root-to-leaf paths with the given sum (BFS with queue). Args: root: The root of the binary tree. - sum: The target sum. + target: The target sum. Returns: A list of paths, where each path is a list of node values. @@ -99,6 +103,7 @@ def path_sum3(root: TreeNode | None, sum: int) -> list[list[int]]: Examples: >>> path_sum3(None, 0) [] + """ if root is None: return [] @@ -107,10 +112,10 @@ def path_sum3(root: TreeNode | None, sum: int) -> list[list[int]]: queue: deque[tuple[TreeNode, int, list[int]]] = deque([initial]) while queue: node, val, path = queue.popleft() - if node.left is None and node.right is None and val == sum: + if node.left is None and node.right is None and val == target: result.append(path) if node.left is not None: - queue.append((node.left, val + node.left.val, path + [node.left.val])) + queue.append((node.left, val + node.left.val, [*path, node.left.val])) if node.right is not None: - queue.append((node.right, val + node.right.val, path + [node.right.val])) + queue.append((node.right, val + node.right.val, [*path, node.right.val])) return result diff --git a/algorithms/tree/pretty_print.py b/algorithms/tree/pretty_print.py index 7bc8a3529..325d5ea80 100644 --- a/algorithms/tree/pretty_print.py +++ b/algorithms/tree/pretty_print.py @@ -1,5 +1,4 @@ -""" -Pretty Print Tree +"""Pretty Print Tree. Prints a dictionary-based tree structure in a human-readable format showing keys and their nested elements. @@ -29,11 +28,11 @@ def tree_print(tree: dict) -> list[str]: Examples: >>> tree_print({"a": ["Adam", "Book", 4]}) ['a -> Adam -> Book -> 4'] + """ lines: list[str] = [] - for key in tree: + for key, tree_element in tree.items(): parts = [str(key)] - tree_element = tree[key] for sub_elem in tree_element: parts.append(str(sub_elem)) lines.append(" -> ".join(parts)) diff --git a/algorithms/tree/same_tree.py b/algorithms/tree/same_tree.py index 71720d4f6..b3c71c3d2 100644 --- a/algorithms/tree/same_tree.py +++ b/algorithms/tree/same_tree.py @@ -1,5 +1,4 @@ -""" -Same Tree +"""Same Tree. Given two binary trees, check if they are structurally identical and the nodes have the same values. @@ -13,7 +12,10 @@ from __future__ import annotations -from algorithms.tree.tree import TreeNode +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from algorithms.tree.tree import TreeNode def is_same_tree(tree_p: TreeNode | None, tree_q: TreeNode | None) -> bool: @@ -30,11 +32,12 @@ def is_same_tree(tree_p: TreeNode | None, tree_q: TreeNode | None) -> bool: Examples: >>> is_same_tree(None, None) True + """ if tree_p is None and tree_q is None: return True if tree_p is not None and tree_q is not None and tree_p.val == tree_q.val: return is_same_tree(tree_p.left, tree_q.left) and is_same_tree( - tree_p.right, tree_q.right + tree_p.right, tree_q.right, ) return False diff --git a/algorithms/tree/traversal_inorder.py b/algorithms/tree/traversal_inorder.py index 3ad6a8a86..020e4bde0 100644 --- a/algorithms/tree/traversal_inorder.py +++ b/algorithms/tree/traversal_inorder.py @@ -1,17 +1,15 @@ -""" -Time complexity : O(n) -""" +"""Time complexity : O(n).""" class Node: - def __init__(self, val, left=None, right=None): + def __init__(self, val, left=None, right=None) -> None: self.val = val self.left = left self.right = right def inorder(root): - """In order function""" + """In order function.""" res = [] if not root: return res @@ -27,7 +25,7 @@ def inorder(root): def inorder_rec(root, res=None): - """Recursive Implementation""" + """Recursive Implementation.""" if root is None: return [] if res is None: @@ -36,19 +34,3 @@ def inorder_rec(root, res=None): res.append(root.val) inorder_rec(root.right, res) return res - - -if __name__ == "__main__": - n1 = Node(100) - n2 = Node(50) - n3 = Node(150) - n4 = Node(25) - n5 = Node(75) - n6 = Node(125) - n7 = Node(175) - n1.left, n1.right = n2, n3 - n2.left, n2.right = n4, n5 - n3.left, n3.right = n6, n7 - - assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175] - assert inorder_rec(n1) == [25, 50, 75, 100, 125, 150, 175] diff --git a/algorithms/tree/traversal_level_order.py b/algorithms/tree/traversal_level_order.py index 1aa7311fb..36043ffb4 100644 --- a/algorithms/tree/traversal_level_order.py +++ b/algorithms/tree/traversal_level_order.py @@ -1,5 +1,4 @@ -""" -Given a binary tree, return the level order traversal of +"""Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level). For example: diff --git a/algorithms/tree/traversal_postorder.py b/algorithms/tree/traversal_postorder.py index 56b403e19..93cec6716 100644 --- a/algorithms/tree/traversal_postorder.py +++ b/algorithms/tree/traversal_postorder.py @@ -1,10 +1,8 @@ -""" -Time complexity : O(n) -""" +"""Time complexity : O(n).""" class Node: - def __init__(self, val, left=None, right=None): + def __init__(self, val, left=None, right=None) -> None: self.val = val self.left = left self.right = right diff --git a/algorithms/tree/traversal_preorder.py b/algorithms/tree/traversal_preorder.py index f841a8523..7a493e9c7 100644 --- a/algorithms/tree/traversal_preorder.py +++ b/algorithms/tree/traversal_preorder.py @@ -1,19 +1,17 @@ -""" -Time complexity : O(n) -""" +"""Time complexity : O(n).""" class Node: - """This is a class of Node""" + """This is a class of Node.""" - def __init__(self, val, left=None, right=None): + def __init__(self, val, left=None, right=None) -> None: self.val = val self.left = left self.right = right def preorder(root): - """Function to Preorder""" + """Function to Preorder.""" res = [] if not root: return res @@ -30,7 +28,7 @@ def preorder(root): def preorder_rec(root, res=None): - """Recursive Implementation""" + """Recursive Implementation.""" if root is None: return [] if res is None: diff --git a/algorithms/tree/traversal_zigzag.py b/algorithms/tree/traversal_zigzag.py index 433a71e04..f8c297b9c 100644 --- a/algorithms/tree/traversal_zigzag.py +++ b/algorithms/tree/traversal_zigzag.py @@ -1,5 +1,4 @@ -""" -Given a binary tree, return the zigzag level order traversal +"""Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between). diff --git a/algorithms/tree/trie_add_and_search.py b/algorithms/tree/trie_add_and_search.py index ac2172569..1f0e4c943 100644 --- a/algorithms/tree/trie_add_and_search.py +++ b/algorithms/tree/trie_add_and_search.py @@ -1,5 +1,4 @@ -""" -We are asked to design an efficient data structure +"""We are asked to design an efficient data structure that allows us to add and search for words. The search can be a literal word or regular expression containing “.”, where “.” can be any letter. @@ -12,23 +11,24 @@ search(“bad”) -> true search(“.ad”) -> true search(“b..”) -> true + """ import collections class TrieNode: - def __init__(self, letter, is_terminal=False): - self.children = dict() + def __init__(self, letter, is_terminal=False) -> None: + self.children = {} self.letter = letter self.is_terminal = is_terminal class WordDictionary: - def __init__(self): + def __init__(self) -> None: self.root = TrieNode("") - def add_word(self, word): + def add_word(self, word) -> None: cur = self.root for letter in word: if letter not in cur.children: @@ -60,10 +60,10 @@ def search(self, word, node=None): class WordDictionary2: - def __init__(self): + def __init__(self) -> None: self.word_dict = collections.defaultdict(list) - def add_word(self, word): + def add_word(self, word) -> None: if word: self.word_dict[len(word)].append(word)