From b3f7790236f9541a79be389bfcc35d9146942a46 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 20 Oct 2023 20:45:08 -0400 Subject: [PATCH 1/5] Consolidate binary exponentiation files --- maths/binary_exp_mod.py | 28 ------ maths/binary_exponentiation.py | 160 +++++++++++++++++++++++++------ maths/binary_exponentiation_2.py | 61 ------------ 3 files changed, 130 insertions(+), 119 deletions(-) delete mode 100644 maths/binary_exp_mod.py delete mode 100644 maths/binary_exponentiation_2.py diff --git a/maths/binary_exp_mod.py b/maths/binary_exp_mod.py deleted file mode 100644 index 8893182a3496..000000000000 --- a/maths/binary_exp_mod.py +++ /dev/null @@ -1,28 +0,0 @@ -def bin_exp_mod(a: int, n: int, b: int) -> int: - """ - >>> bin_exp_mod(3, 4, 5) - 1 - >>> bin_exp_mod(7, 13, 10) - 7 - """ - # mod b - assert b != 0, "This cannot accept modulo that is == 0" - if n == 0: - return 1 - - if n % 2 == 1: - return (bin_exp_mod(a, n - 1, b) * a) % b - - r = bin_exp_mod(a, n // 2, b) - return (r * r) % b - - -if __name__ == "__main__": - try: - BASE = int(input("Enter Base : ").strip()) - POWER = int(input("Enter Power : ").strip()) - MODULO = int(input("Enter Modulo : ").strip()) - except ValueError: - print("Invalid literal for integer") - - print(bin_exp_mod(BASE, POWER, MODULO)) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index f613767f547e..84c86480cbd6 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -1,36 +1,139 @@ -"""Binary Exponentiation.""" +""" +Binary Exponentiation -# Author : Junth Basnet -# Time Complexity : O(logn) +This is a method to find a^b in O(log b) time complexity and is one of the most commonly +used methods of exponentiation. The method is also useful for modular exponentiation, +when the solution to (a^b) % c is required. +To calculate a^b: +- If b is even, then a b = (a * a)^(b / 2) +- If b is odd, then a^b = a * a^(b - 1) +Repeat until b = 1 or b = 0 -def binary_exponentiation(a: int, n: int) -> int: +For modular exponentiation, we use the fact that (a * b) % c = ((a % c) * (b % c)) % c +""" + + +def binary_exp_recursive(base: int, exponent: int) -> int: """ - Compute a number raised by some quantity - >>> binary_exponentiation(-1, 3) + >>> binary_exp_recursive(3, 5) + 243 + >>> binary_exp_recursive(-1, 3) -1 - >>> binary_exponentiation(-1, 4) + >>> binary_exp_recursive(0, 5) + 0 + >>> binary_exp_recursive(3, 1) + 3 + >>> binary_exp_recursive(3, 0) 1 - >>> binary_exponentiation(2, 2) - 4 - >>> binary_exponentiation(3, 5) + >>> binary_exp_recursive(3, -1) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + + if exponent == 0: + return 1 + + if exponent % 2 == 1: + return binary_exp_recursive(base, exponent - 1) * base + + b = binary_exp_recursive(base, exponent // 2) + return b * b + + +def binary_exp_iterative(base: int, exponent: int) -> int: + """ + >>> binary_exp_recursive(3, 5) 243 - >>> binary_exponentiation(10, 3) - 1000 - >>> binary_exponentiation(5e3, 1) - 5000.0 - >>> binary_exponentiation(-5e3, 1) - -5000.0 - """ - if n == 0: + >>> binary_exp_recursive(-1, 3) + -1 + >>> binary_exp_recursive(0, 5) + 0 + >>> binary_exp_recursive(3, 1) + 3 + >>> binary_exp_recursive(3, 0) + 1 + >>> binary_exp_recursive(3, -1) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + + res = 1 + while exponent > 0: + if exponent & 1: + res *= base + + base *= base + exponent >>= 1 + + return res + + +def binary_exp_mod_recursive(base: int, exponent: int, modulus: int) -> int: + """ + >>> binary_exp_mod_recursive(3, 4, 5) + 1 + >>> binary_exp_mod_recursive(7, 13, 10) + 7 + >>> binary_exp_mod_recursive(7, -1, 10) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + >>> binary_exp_mod_recursive(7, 13, 0) + Traceback (most recent call last): + ... + ValueError: Modulus must be a positive integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + if modulus <= 0: + raise ValueError("Modulus must be a positive integer") + + if exponent == 0: return 1 - elif n % 2 == 1: - return binary_exponentiation(a, n - 1) * a + if exponent % 2 == 1: + return (binary_exp_mod_recursive(base, exponent - 1, modulus) * base) % modulus + + r = binary_exp_mod_recursive(base, exponent // 2, modulus) + return (r * r) % modulus + + +def binary_exp_mod_iterative(base: int, exponent: int, modulus: int) -> int: + """ + >>> binary_exp_mod_recursive(3, 4, 5) + 1 + >>> binary_exp_mod_recursive(7, 13, 10) + 7 + >>> binary_exp_mod_recursive(7, -1, 10) + Traceback (most recent call last): + ... + ValueError: Exponent must be a non-negative integer + >>> binary_exp_mod_recursive(7, 13, 0) + Traceback (most recent call last): + ... + ValueError: Modulus must be a positive integer + """ + if exponent < 0: + raise ValueError("Exponent must be a non-negative integer") + if modulus <= 0: + raise ValueError("Modulus must be a positive integer") + + res = 1 + while exponent > 0: + if exponent & 1: + res = ((res % modulus) * (base % modulus)) % modulus + + base *= base + exponent >>= 1 - else: - b = binary_exponentiation(a, n // 2) - return b * b + return res if __name__ == "__main__": @@ -38,11 +141,8 @@ def binary_exponentiation(a: int, n: int) -> int: doctest.testmod() - try: - BASE = int(float(input("Enter Base : ").strip())) - POWER = int(input("Enter Power : ").strip()) - except ValueError: - print("Invalid literal for integer") + BASE = int(input("Enter base: ").strip()) + POWER = int(input("Enter power: ").strip()) - RESULT = binary_exponentiation(BASE, POWER) - print(f"{BASE}^({POWER}) : {RESULT}") + RESULT = binary_exp_recursive(BASE, POWER) + print(f"{BASE}^({POWER}): {RESULT}") diff --git a/maths/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py deleted file mode 100644 index edb6b66b2594..000000000000 --- a/maths/binary_exponentiation_2.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Binary Exponentiation -This is a method to find a^b in O(log b) time complexity -This is one of the most commonly used methods of exponentiation -It's also useful when the solution to (a^b) % c is required because a, b, c may be -over the computer's calculation limits - -Let's say you need to calculate a ^ b -- RULE 1 : a ^ b = (a*a) ^ (b/2) ---- example : 4 ^ 4 = (4*4) ^ (4/2) = 16 ^ 2 -- RULE 2 : IF b is odd, then a ^ b = a * (a ^ (b - 1)), where b - 1 is even -Once b is even, repeat the process until b = 1 or b = 0, because a^1 = a and a^0 = 1 - -For modular exponentiation, we use the fact that (a*b) % c = ((a%c) * (b%c)) % c -Now apply RULE 1 or 2 as required - -@author chinmoy159 -""" - - -def b_expo(a: int, b: int) -> int: - """ - >>> b_expo(2, 10) - 1024 - >>> b_expo(9, 0) - 1 - >>> b_expo(0, 12) - 0 - >>> b_expo(4, 12) - 16777216 - """ - res = 1 - while b > 0: - if b & 1: - res *= a - - a *= a - b >>= 1 - - return res - - -def b_expo_mod(a: int, b: int, c: int) -> int: - """ - >>> b_expo_mod(2, 10, 1000000007) - 1024 - >>> b_expo_mod(11, 13, 19) - 11 - >>> b_expo_mod(0, 19, 20) - 0 - >>> b_expo_mod(15, 5, 4) - 3 - """ - res = 1 - while b > 0: - if b & 1: - res = ((res % c) * (a % c)) % c - - a *= a - b >>= 1 - - return res From 72e21bed3aa3836bd7fcc350ed6aa26782825ff6 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Sat, 21 Oct 2023 00:45:55 +0000 Subject: [PATCH 2/5] updating DIRECTORY.md --- DIRECTORY.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 5b7ca856ea15..199f5d8ac16e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -583,9 +583,7 @@ * [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py) * [Base Neg2 Conversion](maths/base_neg2_conversion.py) * [Basic Maths](maths/basic_maths.py) - * [Binary Exp Mod](maths/binary_exp_mod.py) * [Binary Exponentiation](maths/binary_exponentiation.py) - * [Binary Exponentiation 2](maths/binary_exponentiation_2.py) * [Binary Multiplication](maths/binary_multiplication.py) * [Binomial Coefficient](maths/binomial_coefficient.py) * [Binomial Distribution](maths/binomial_distribution.py) From b15c87a5814ab02e7c379e5b966e4df31c4896a8 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Fri, 20 Oct 2023 20:53:14 -0400 Subject: [PATCH 3/5] Fix typos in doctests --- maths/binary_exponentiation.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 84c86480cbd6..2a564e1d24ec 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -46,17 +46,17 @@ def binary_exp_recursive(base: int, exponent: int) -> int: def binary_exp_iterative(base: int, exponent: int) -> int: """ - >>> binary_exp_recursive(3, 5) + >>> binary_exp_iterative(3, 5) 243 - >>> binary_exp_recursive(-1, 3) + >>> binary_exp_iterative(-1, 3) -1 - >>> binary_exp_recursive(0, 5) + >>> binary_exp_iterative(0, 5) 0 - >>> binary_exp_recursive(3, 1) + >>> binary_exp_iterative(3, 1) 3 - >>> binary_exp_recursive(3, 0) + >>> binary_exp_iterative(3, 0) 1 - >>> binary_exp_recursive(3, -1) + >>> binary_exp_iterative(3, -1) Traceback (most recent call last): ... ValueError: Exponent must be a non-negative integer @@ -107,15 +107,15 @@ def binary_exp_mod_recursive(base: int, exponent: int, modulus: int) -> int: def binary_exp_mod_iterative(base: int, exponent: int, modulus: int) -> int: """ - >>> binary_exp_mod_recursive(3, 4, 5) + >>> binary_exp_mod_iterative(3, 4, 5) 1 - >>> binary_exp_mod_recursive(7, 13, 10) + >>> binary_exp_mod_iterative(7, 13, 10) 7 - >>> binary_exp_mod_recursive(7, -1, 10) + >>> binary_exp_mod_iterative(7, -1, 10) Traceback (most recent call last): ... ValueError: Exponent must be a non-negative integer - >>> binary_exp_mod_recursive(7, 13, 0) + >>> binary_exp_mod_iterative(7, 13, 0) Traceback (most recent call last): ... ValueError: Modulus must be a positive integer From cf6c7d2a00f1a16d1abb0b5452a0407e46700c66 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 21 Oct 2023 13:09:47 -0400 Subject: [PATCH 4/5] Add suggestions from code review --- maths/binary_exponentiation.py | 56 +++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 2a564e1d24ec..0db94f51d02c 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -6,7 +6,7 @@ when the solution to (a^b) % c is required. To calculate a^b: -- If b is even, then a b = (a * a)^(b / 2) +- If b is even, then a^b = (a * a)^(b / 2) - If b is odd, then a^b = a * a^(b - 1) Repeat until b = 1 or b = 0 @@ -14,10 +14,14 @@ """ -def binary_exp_recursive(base: int, exponent: int) -> int: +def binary_exp_recursive(base: float, exponent: int) -> float: """ + Computes a^b recursively, where a is the base and b is the exponent + >>> binary_exp_recursive(3, 5) 243 + >>> binary_exp_recursive(11, 13) + 34522712143931 >>> binary_exp_recursive(-1, 3) -1 >>> binary_exp_recursive(0, 5) @@ -26,6 +30,8 @@ def binary_exp_recursive(base: int, exponent: int) -> int: 3 >>> binary_exp_recursive(3, 0) 1 + >>> binary_exp_recursive(1.5, 4) + 5.0625 >>> binary_exp_recursive(3, -1) Traceback (most recent call last): ... @@ -44,10 +50,14 @@ def binary_exp_recursive(base: int, exponent: int) -> int: return b * b -def binary_exp_iterative(base: int, exponent: int) -> int: +def binary_exp_iterative(base: float, exponent: int) -> float: """ + Computes a^b iteratively, where a is the base and b is the exponent + >>> binary_exp_iterative(3, 5) 243 + >>> binary_exp_iterative(11, 13) + 34522712143931 >>> binary_exp_iterative(-1, 3) -1 >>> binary_exp_iterative(0, 5) @@ -56,6 +66,8 @@ def binary_exp_iterative(base: int, exponent: int) -> int: 3 >>> binary_exp_iterative(3, 0) 1 + >>> binary_exp_iterative(1.5, 4) + 5.0625 >>> binary_exp_iterative(3, -1) Traceback (most recent call last): ... @@ -75,12 +87,17 @@ def binary_exp_iterative(base: int, exponent: int) -> int: return res -def binary_exp_mod_recursive(base: int, exponent: int, modulus: int) -> int: +def binary_exp_mod_recursive(base: float, exponent: int, modulus: int) -> float: """ + Computes a^b % c recursively, where a is the base, b is the exponent, and c is the + modulus + >>> binary_exp_mod_recursive(3, 4, 5) 1 - >>> binary_exp_mod_recursive(7, 13, 10) - 7 + >>> binary_exp_mod_recursive(11, 13, 7) + 4 + >>> binary_exp_mod_recursive(1.5, 4, 3) + 2.0625 >>> binary_exp_mod_recursive(7, -1, 10) Traceback (most recent call last): ... @@ -105,12 +122,17 @@ def binary_exp_mod_recursive(base: int, exponent: int, modulus: int) -> int: return (r * r) % modulus -def binary_exp_mod_iterative(base: int, exponent: int, modulus: int) -> int: +def binary_exp_mod_iterative(base: float, exponent: int, modulus: int) -> float: """ + Computes a^b % c iteratively, where a is the base, b is the exponent, and c is the + modulus + >>> binary_exp_mod_iterative(3, 4, 5) 1 - >>> binary_exp_mod_iterative(7, 13, 10) - 7 + >>> binary_exp_mod_iterative(11, 13, 7) + 4 + >>> binary_exp_mod_iterative(1.5, 4, 3) + 2.0625 >>> binary_exp_mod_iterative(7, -1, 10) Traceback (most recent call last): ... @@ -137,12 +159,16 @@ def binary_exp_mod_iterative(base: int, exponent: int, modulus: int) -> int: if __name__ == "__main__": - import doctest + from timeit import timeit - doctest.testmod() + setup = "from __main__ import *" + runs = 1000 - BASE = int(input("Enter base: ").strip()) - POWER = int(input("Enter power: ").strip()) + a = 1269380576 + b = 374 + c = 34 - RESULT = binary_exp_recursive(BASE, POWER) - print(f"{BASE}^({POWER}): {RESULT}") + print(timeit(f"binary_exp_recursive({a}, {b})", setup=setup, number=runs)) + print(timeit(f"binary_exp_iterative({a}, {b})", setup=setup, number=runs)) + print(timeit(f"binary_exp_mod_recursive({a}, {b})", setup=setup, number=runs)) + print(timeit(f"binary_exp_mod_iterative({a}, {b})", setup=setup, number=runs)) From c2769e94d385828fb9b74567a0a7efd58a4200f0 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 21 Oct 2023 13:20:05 -0400 Subject: [PATCH 5/5] Fix timeit benchmarks --- maths/binary_exponentiation.py | 40 ++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 0db94f51d02c..51ce86d26c41 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -76,7 +76,7 @@ def binary_exp_iterative(base: float, exponent: int) -> float: if exponent < 0: raise ValueError("Exponent must be a non-negative integer") - res = 1 + res: int | float = 1 while exponent > 0: if exponent & 1: res *= base @@ -147,7 +147,7 @@ def binary_exp_mod_iterative(base: float, exponent: int, modulus: int) -> float: if modulus <= 0: raise ValueError("Modulus must be a positive integer") - res = 1 + res: int | float = 1 while exponent > 0: if exponent & 1: res = ((res % modulus) * (base % modulus)) % modulus @@ -161,14 +161,36 @@ def binary_exp_mod_iterative(base: float, exponent: int, modulus: int) -> float: if __name__ == "__main__": from timeit import timeit - setup = "from __main__ import *" - runs = 1000 - a = 1269380576 b = 374 c = 34 - print(timeit(f"binary_exp_recursive({a}, {b})", setup=setup, number=runs)) - print(timeit(f"binary_exp_iterative({a}, {b})", setup=setup, number=runs)) - print(timeit(f"binary_exp_mod_recursive({a}, {b})", setup=setup, number=runs)) - print(timeit(f"binary_exp_mod_iterative({a}, {b})", setup=setup, number=runs)) + runs = 100_000 + print( + timeit( + f"binary_exp_recursive({a}, {b})", + setup="from __main__ import binary_exp_recursive", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_iterative({a}, {b})", + setup="from __main__ import binary_exp_iterative", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_mod_recursive({a}, {b}, {c})", + setup="from __main__ import binary_exp_mod_recursive", + number=runs, + ) + ) + print( + timeit( + f"binary_exp_mod_iterative({a}, {b}, {c})", + setup="from __main__ import binary_exp_mod_iterative", + number=runs, + ) + )