diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py index 6ae66b5b0a77..650017b7ae10 100644 --- a/boolean_algebra/and_gate.py +++ b/boolean_algebra/and_gate.py @@ -1,8 +1,8 @@ """ -An AND Gate is a logic gate in boolean algebra which results to 1 (True) if both the -inputs are 1, and 0 (False) otherwise. +An AND Gate is a logic gate in boolean algebra which results to 1 (True) if all the +inputs are 1 (True), and 0 (False) otherwise. -Following is the truth table of an AND Gate: +Following is the truth table of a Two Input AND Gate: ------------------------------ | Input 1 | Input 2 | Output | ------------------------------ @@ -12,7 +12,7 @@ | 1 | 1 | 1 | ------------------------------ -Refer - https://www.geeksforgeeks.org/logic-gates-in-python/ +Refer - https://www.geeksforgeeks.org/logic-gates/ """ @@ -32,6 +32,18 @@ def and_gate(input_1: int, input_2: int) -> int: return int(input_1 and input_2) +def n_input_and_gate(inputs: list[int]) -> int: + """ + Calculate AND of a list of input values + + >>> n_input_and_gate([1, 0, 1, 1, 0]) + 0 + >>> n_input_and_gate([1, 1, 1, 1, 1]) + 1 + """ + return int(all(inputs)) + + if __name__ == "__main__": import doctest diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 651037703b95..ea0e31256903 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -15,6 +15,8 @@ def swap(a: int, b: int) -> tuple[int, int]: (4, 3) >>> swap(67, 12) (12, 67) + >>> swap(3,-4) + (-4, 3) """ a ^= b b ^= a @@ -25,6 +27,23 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ creating sparse table which saves each nodes 2^i-th parent + >>> max_node = 6 + >>> parent = [[0, 0, 1, 1, 2, 2, 3]] + [[0] * 7 for _ in range(19)] + >>> parent = create_sparse(max_node=max_node, parent=parent) + >>> parent[0] + [0, 0, 1, 1, 2, 2, 3] + >>> parent[1] + [0, 0, 0, 0, 1, 1, 1] + >>> parent[2] + [0, 0, 0, 0, 0, 0, 0] + + >>> max_node = 1 + >>> parent = [[0, 0]] + [[0] * 2 for _ in range(19)] + >>> parent = create_sparse(max_node=max_node, parent=parent) + >>> parent[0] + [0, 0] + >>> parent[1] + [0, 0] """ j = 1 while (1 << j) < max_node: @@ -38,6 +57,21 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: + """ + Return the lowest common ancestor between u and v + + >>> level = [-1, 0, 1, 1, 2, 2, 2] + >>> parent = [[0, 0, 1, 1, 2, 2, 3],[0, 0, 0, 0, 1, 1, 1]] + \ + [[0] * 7 for _ in range(17)] + >>> lowest_common_ancestor(u=4, v=5, level=level, parent=parent) + 2 + >>> lowest_common_ancestor(u=4, v=6, level=level, parent=parent) + 1 + >>> lowest_common_ancestor(u=2, v=3, level=level, parent=parent) + 1 + >>> lowest_common_ancestor(u=6, v=6, level=level, parent=parent) + 6 + """ # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -68,6 +102,26 @@ def breadth_first_search( sets every nodes direct parent parent of root node is set to 0 calculates depth of each node from root node + >>> level = [-1] * 7 + >>> parent = [[0] * 7 for _ in range(20)] + >>> graph = {1: [2, 3], 2: [4, 5], 3: [6], 4: [], 5: [], 6: []} + >>> level, parent = breadth_first_search( + ... level=level, parent=parent, max_node=6, graph=graph, root=1) + >>> level + [-1, 0, 1, 1, 2, 2, 2] + >>> parent[0] + [0, 0, 1, 1, 2, 2, 3] + + + >>> level = [-1] * 2 + >>> parent = [[0] * 2 for _ in range(20)] + >>> graph = {1: []} + >>> level, parent = breadth_first_search( + ... level=level, parent=parent, max_node=1, graph=graph, root=1) + >>> level + [-1, 0] + >>> parent[0] + [0, 0] """ level[root] = 0 q: Queue[int] = Queue(maxsize=max_node) diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py index ea5233eb2d17..3ba83f3d9f03 100644 --- a/dynamic_programming/longest_common_substring.py +++ b/dynamic_programming/longest_common_substring.py @@ -43,22 +43,25 @@ def longest_common_substring(text1: str, text2: str) -> str: if not (isinstance(text1, str) and isinstance(text2, str)): raise ValueError("longest_common_substring() takes two strings for inputs") + if not text1 or not text2: + return "" + text1_length = len(text1) text2_length = len(text2) dp = [[0] * (text2_length + 1) for _ in range(text1_length + 1)] - ans_index = 0 - ans_length = 0 + end_pos = 0 + max_length = 0 for i in range(1, text1_length + 1): for j in range(1, text2_length + 1): if text1[i - 1] == text2[j - 1]: dp[i][j] = 1 + dp[i - 1][j - 1] - if dp[i][j] > ans_length: - ans_index = i - ans_length = dp[i][j] + if dp[i][j] > max_length: + end_pos = i + max_length = dp[i][j] - return text1[ans_index - ans_length : ans_index] + return text1[end_pos - max_length : end_pos] if __name__ == "__main__": diff --git a/project_euler/problem_095/__init__.py b/project_euler/problem_095/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_095/sol1.py b/project_euler/problem_095/sol1.py new file mode 100644 index 000000000000..82d84c5544de --- /dev/null +++ b/project_euler/problem_095/sol1.py @@ -0,0 +1,164 @@ +""" +Project Euler Problem 95: https://projecteuler.net/problem=95 + +Amicable Chains + +The proper divisors of a number are all the divisors excluding the number itself. +For example, the proper divisors of 28 are 1, 2, 4, 7, and 14. +As the sum of these divisors is equal to 28, we call it a perfect number. + +Interestingly the sum of the proper divisors of 220 is 284 and +the sum of the proper divisors of 284 is 220, forming a chain of two numbers. +For this reason, 220 and 284 are called an amicable pair. + +Perhaps less well known are longer chains. +For example, starting with 12496, we form a chain of five numbers: + 12496 -> 14288 -> 15472 -> 14536 -> 14264 (-> 12496 -> ...) + +Since this chain returns to its starting point, it is called an amicable chain. + +Find the smallest member of the longest amicable chain with +no element exceeding one million. + +Solution is doing the following: +- Get relevant prime numbers +- Iterate over product combination of prime numbers to generate all non-prime + numbers up to max number, by keeping track of prime factors +- Calculate the sum of factors for each number +- Iterate over found some factors to find longest chain +""" + +from math import isqrt + + +def generate_primes(max_num: int) -> list[int]: + """ + Calculates the list of primes up to and including `max_num`. + + >>> generate_primes(6) + [2, 3, 5] + """ + are_primes = [True] * (max_num + 1) + are_primes[0] = are_primes[1] = False + for i in range(2, isqrt(max_num) + 1): + if are_primes[i]: + for j in range(i * i, max_num + 1, i): + are_primes[j] = False + + return [prime for prime, is_prime in enumerate(are_primes) if is_prime] + + +def multiply( + chain: list[int], + primes: list[int], + min_prime_idx: int, + prev_num: int, + max_num: int, + prev_sum: int, + primes_degrees: dict[int, int], +) -> None: + """ + Run over all prime combinations to generate non-prime numbers. + + >>> chain = [0] * 3 + >>> primes_degrees = {} + >>> multiply( + ... chain=chain, + ... primes=[2], + ... min_prime_idx=0, + ... prev_num=1, + ... max_num=2, + ... prev_sum=0, + ... primes_degrees=primes_degrees, + ... ) + >>> chain + [0, 0, 1] + >>> primes_degrees + {2: 1} + """ + + min_prime = primes[min_prime_idx] + num = prev_num * min_prime + + min_prime_degree = primes_degrees.get(min_prime, 0) + min_prime_degree += 1 + primes_degrees[min_prime] = min_prime_degree + + new_sum = prev_sum * min_prime + (prev_sum + prev_num) * (min_prime - 1) // ( + min_prime**min_prime_degree - 1 + ) + chain[num] = new_sum + + for prime_idx in range(min_prime_idx, len(primes)): + if primes[prime_idx] * num > max_num: + break + + multiply( + chain=chain, + primes=primes, + min_prime_idx=prime_idx, + prev_num=num, + max_num=max_num, + prev_sum=new_sum, + primes_degrees=primes_degrees.copy(), + ) + + +def find_longest_chain(chain: list[int], max_num: int) -> int: + """ + Finds the smallest element of longest chain + + >>> find_longest_chain(chain=[0, 0, 0, 0, 0, 0, 6], max_num=6) + 6 + """ + + max_len = 0 + min_elem = 0 + for start in range(2, len(chain)): + visited = {start} + elem = chain[start] + length = 1 + + while elem > 1 and elem <= max_num and elem not in visited: + visited.add(elem) + elem = chain[elem] + length += 1 + + if elem == start and length > max_len: + max_len = length + min_elem = start + + return min_elem + + +def solution(max_num: int = 1000000) -> int: + """ + Runs the calculation for numbers <= `max_num`. + + >>> solution(10) + 6 + >>> solution(200000) + 12496 + """ + + primes = generate_primes(max_num) + chain = [0] * (max_num + 1) + for prime_idx, prime in enumerate(primes): + if prime**2 > max_num: + break + + multiply( + chain=chain, + primes=primes, + min_prime_idx=prime_idx, + prev_num=1, + max_num=max_num, + prev_sum=0, + primes_degrees={}, + ) + + return find_longest_chain(chain=chain, max_num=max_num) + + +if __name__ == "__main__": + print(f"{solution() = }")