Skip to content

Commit b241790

Browse files
committed
Fix all ruff linting issues
- Use modern type hint syntax (int | None instead of Optional[int]) - Fix import order (removed unused typing import) - Fix line length issues by breaking long lines - Remove trailing whitespace and blank line whitespace - Replace unittest assertions with regular assert statements - Fix unused variable issues - Rename unused loop variables to _variable - Add missing newlines at end of files - All ruff checks now pass while maintaining functionality
1 parent b31cc12 commit b241790

File tree

2 files changed

+72
-56
lines changed

2 files changed

+72
-56
lines changed

maths/pollard_rho_discrete_log.py

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
from typing import Optional
21
import math
32
import random
43

54

6-
def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> Optional[int]:
5+
def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> int | None:
76
"""
87
Solve for x in the discrete logarithm problem: base^x ≡ target (mod modulus)
98
using Pollard's Rho algorithm.
109
11-
This is a probabilistic algorithm that finds discrete logarithms in O(√modulus) time.
12-
The algorithm may not always find a solution in a single run due to its
10+
This is a probabilistic algorithm that finds discrete logarithms in
11+
O(√modulus) time.
12+
The algorithm may not always find a solution in a single run due to its
1313
probabilistic nature, but it will find the correct answer when it succeeds.
14-
14+
1515
More info: https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm_for_logarithms
1616
1717
Parameters
@@ -25,23 +25,23 @@ def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> Optional[
2525
2626
Returns
2727
-------
28-
Optional[int]
28+
int | None
2929
The discrete log x if found, otherwise None.
3030
3131
Examples
3232
--------
3333
>>> result = pollards_rho_discrete_log(2, 22, 29)
3434
>>> result is not None and pow(2, result, 29) == 22
3535
True
36-
36+
3737
>>> result = pollards_rho_discrete_log(3, 9, 11)
3838
>>> result is not None and pow(3, result, 11) == 9
3939
True
40-
40+
4141
>>> result = pollards_rho_discrete_log(5, 3, 7)
4242
>>> result is not None and pow(5, result, 7) == 3
4343
True
44-
44+
4545
>>> # Case with no solution should return None or fail verification
4646
>>> result = pollards_rho_discrete_log(3, 7, 11)
4747
>>> result is None or pow(3, result, 11) != 7
@@ -53,7 +53,7 @@ def pseudo_random_function(
5353
) -> tuple[int, int, int]:
5454
"""
5555
Pseudo-random function that partitions the search space into 3 sets.
56-
56+
5757
Returns a tuple of (new_value, new_exponent_base, new_exponent_target).
5858
"""
5959
if current_value % 3 == 0:
@@ -80,30 +80,44 @@ def pseudo_random_function(
8080

8181
# Try multiple random starting points to avoid immediate collisions
8282
max_attempts = 50 # Increased attempts for better reliability
83-
84-
for attempt in range(max_attempts):
83+
84+
for _attempt in range(max_attempts):
8585
# Use different starting values to avoid trivial collisions
8686
# current_value represents base^exponent_base * target^exponent_target
8787
random.seed() # Ensure truly random values
8888
exponent_base = random.randint(0, modulus - 2)
8989
exponent_target = random.randint(0, modulus - 2)
90-
90+
9191
# Ensure current_value = base^exponent_base * target^exponent_target mod modulus
92-
current_value = (pow(base, exponent_base, modulus) * pow(target, exponent_target, modulus)) % modulus
93-
92+
current_value = (
93+
pow(base, exponent_base, modulus) * pow(target, exponent_target, modulus)
94+
) % modulus
95+
9496
# Skip if current_value is 0 or 1 (problematic starting points)
9597
if current_value <= 1:
9698
continue
97-
99+
98100
# Tortoise and hare start at same position
99-
tortoise_value, tortoise_exp_base, tortoise_exp_target = current_value, exponent_base, exponent_target
100-
hare_value, hare_exp_base, hare_exp_target = current_value, exponent_base, exponent_target
101+
tortoise_value, tortoise_exp_base, tortoise_exp_target = (
102+
current_value,
103+
exponent_base,
104+
exponent_target,
105+
)
106+
hare_value, hare_exp_base, hare_exp_target = (
107+
current_value,
108+
exponent_base,
109+
exponent_target,
110+
)
101111

102112
# Increased iteration limit for better coverage
103113
max_iterations = max(int(math.sqrt(modulus)) * 2, modulus // 2)
104114
for i in range(1, max_iterations):
105115
# Tortoise: one step
106-
tortoise_value, tortoise_exp_base, tortoise_exp_target = pseudo_random_function(
116+
(
117+
tortoise_value,
118+
tortoise_exp_base,
119+
tortoise_exp_target,
120+
) = pseudo_random_function(
107121
tortoise_value, tortoise_exp_base, tortoise_exp_target
108122
)
109123
# Hare: two steps
@@ -113,8 +127,12 @@ def pseudo_random_function(
113127

114128
if tortoise_value == hare_value and i > 1: # Avoid immediate collision
115129
# Collision found
116-
exponent_difference = (tortoise_exp_base - hare_exp_base) % (modulus - 1)
117-
target_difference = (hare_exp_target - tortoise_exp_target) % (modulus - 1)
130+
exponent_difference = (tortoise_exp_base - hare_exp_base) % (
131+
modulus - 1
132+
)
133+
target_difference = (hare_exp_target - tortoise_exp_target) % (
134+
modulus - 1
135+
)
118136

119137
if target_difference == 0:
120138
break # Try with different starting point
@@ -125,22 +143,24 @@ def pseudo_random_function(
125143
except ValueError:
126144
break # No inverse, try different starting point
127145

128-
discrete_log = (exponent_difference * inverse_target_diff) % (modulus - 1)
129-
146+
discrete_log = (exponent_difference * inverse_target_diff) % (
147+
modulus - 1
148+
)
149+
130150
# Verify the solution
131151
if pow(base, discrete_log, modulus) == target:
132152
return discrete_log
133153
break # This attempt failed, try with different starting point
134-
154+
135155
return None
136156

137157

138158
if __name__ == "__main__":
139159
import doctest
140-
160+
141161
# Run doctests
142162
doctest.testmod(verbose=True)
143-
163+
144164
# Also run the main example
145165
result = pollards_rho_discrete_log(2, 22, 29)
146166
print(f"pollards_rho_discrete_log(2, 22, 29) = {result}")

maths/test_pollard_rho_discrete_log.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
including basic functionality tests, edge cases, and performance validation.
66
"""
77

8-
import unittest
9-
import sys
108
import os
9+
import sys
10+
import unittest
1111

1212
# Add the parent directory to sys.path to import maths module
1313
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -22,35 +22,32 @@ def test_basic_example(self):
2222
"""Test the basic example from the GitHub issue."""
2323
# Since the algorithm is probabilistic, try multiple times
2424
found_solution = False
25-
for attempt in range(5): # Try up to 5 times
25+
for _attempt in range(5): # Try up to 5 times
2626
result = pollards_rho_discrete_log(2, 22, 29)
2727
if result is not None:
2828
# Verify the result is correct
29-
self.assertEqual(pow(2, result, 29), 22)
29+
assert pow(2, result, 29) == 22
3030
found_solution = True
3131
break
32-
33-
self.assertTrue(found_solution,
34-
"Algorithm should find a solution within 5 attempts")
32+
33+
assert found_solution, "Algorithm should find a solution within 5 attempts"
3534

3635
def test_simple_cases(self):
3736
"""Test simple discrete log cases with known answers."""
3837
test_cases = [
3938
(2, 8, 17), # 2^3 ≡ 8 (mod 17)
40-
(5, 3, 7), # 5^5 ≡ 3 (mod 7)
39+
(5, 3, 7), # 5^5 ≡ 3 (mod 7)
4140
(3, 9, 11), # 3^2 ≡ 9 (mod 11)
4241
]
43-
42+
4443
for g, h, p in test_cases:
4544
# Try multiple times due to probabilistic nature
46-
found_solution = False
47-
for attempt in range(3):
45+
for _attempt in range(3):
4846
result = pollards_rho_discrete_log(g, h, p)
4947
if result is not None:
50-
self.assertEqual(pow(g, result, p), h)
51-
found_solution = True
48+
assert pow(g, result, p) == h
5249
break
53-
# Not all cases may have solutions, so we don't assert found_solution
50+
# Not all cases may have solutions, so we don't check for success
5451

5552
def test_no_solution_case(self):
5653
"""Test case where no solution exists."""
@@ -59,19 +56,19 @@ def test_no_solution_case(self):
5956
result = pollards_rho_discrete_log(3, 7, 11)
6057
if result is not None:
6158
# If it returns a result, it must be wrong since no solution exists
62-
self.assertNotEqual(pow(3, result, 11), 7)
59+
assert pow(3, result, 11) != 7
6360

6461
def test_edge_cases(self):
6562
"""Test edge cases and input validation scenarios."""
6663
# g = 1: 1^x ≡ h (mod p) only has solution if h = 1
6764
result = pollards_rho_discrete_log(1, 1, 7)
6865
if result is not None:
69-
self.assertEqual(pow(1, result, 7), 1)
70-
66+
assert pow(1, result, 7) == 1
67+
7168
# h = 1: g^x ≡ 1 (mod p) - looking for the multiplicative order
7269
result = pollards_rho_discrete_log(3, 1, 7)
7370
if result is not None:
74-
self.assertEqual(pow(3, result, 7), 1)
71+
assert pow(3, result, 7) == 1
7572

7673
def test_small_primes(self):
7774
"""Test with small prime moduli."""
@@ -81,44 +78,43 @@ def test_small_primes(self):
8178
(2, 1, 3), # 2^2 ≡ 1 (mod 3)
8279
(3, 2, 5), # 3^3 ≡ 2 (mod 5)
8380
]
84-
81+
8582
for g, h, p in test_cases:
8683
result = pollards_rho_discrete_log(g, h, p)
8784
if result is not None:
8885
# Verify the result is mathematically correct
89-
self.assertEqual(pow(g, result, p), h)
90-
86+
assert pow(g, result, p) == h
87+
9188
def test_larger_examples(self):
9289
"""Test with larger numbers to ensure algorithm scales."""
9390
# Test cases with larger primes
9491
test_cases = [
9592
(2, 15, 31), # Find x where 2^x ≡ 15 (mod 31)
96-
(3, 10, 37), # Find x where 3^x ≡ 10 (mod 37)
93+
(3, 10, 37), # Find x where 3^x ≡ 10 (mod 37)
9794
(5, 17, 41), # Find x where 5^x ≡ 17 (mod 41)
9895
]
99-
96+
10097
for g, h, p in test_cases:
10198
result = pollards_rho_discrete_log(g, h, p)
10299
if result is not None:
103-
self.assertEqual(pow(g, result, p), h)
100+
assert pow(g, result, p) == h
104101

105102
def test_multiple_runs_consistency(self):
106103
"""Test that multiple runs give consistent results."""
107104
# Since the algorithm is probabilistic, run it multiple times
108105
# and ensure any returned result is mathematically correct
109106
g, h, p = 2, 22, 29
110107
results = []
111-
108+
112109
for _ in range(10): # Run 10 times
113110
result = pollards_rho_discrete_log(g, h, p)
114111
if result is not None:
115112
results.append(result)
116-
self.assertEqual(pow(g, result, p), h)
117-
113+
assert pow(g, result, p) == h
114+
118115
# Should find at least one solution in 10 attempts
119-
self.assertGreater(len(results), 0,
120-
"Algorithm should find solution in multiple attempts")
116+
assert len(results) > 0, "Algorithm should find solution in multiple attempts"
121117

122118

123119
if __name__ == "__main__":
124-
unittest.main(verbosity=2)
120+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)