Skip to content

Commit 38d2e98

Browse files
cclaussgithub-actionspoyea
authored
hill_cipher.py: gcd() -> greatest_common_divisor() (#1997)
* hill_cipher.py: gcd() -> greatest_common_divisor() * fixup! Format Python code with psf/black push * import string * updating DIRECTORY.md * Change matrix to array Add more tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law <[email protected]>
1 parent aa120ce commit 38d2e98

File tree

2 files changed

+53
-55
lines changed

2 files changed

+53
-55
lines changed

DIRECTORY.md

+1
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@
603603
* [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py)
604604
* [Sleep Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/sleep_sort.py)
605605
* [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py)
606+
* [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py)
606607
* [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py)
607608
* [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py)
608609
* [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py)

ciphers/hill_cipher.py

+52-55
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""
22
33
Hill Cipher:
4-
The below defined class 'HillCipher' implements the Hill Cipher algorithm.
5-
The Hill Cipher is an algorithm that implements modern linear algebra techniques
6-
In this algorithm, you have an encryption key matrix. This is what will be used
7-
in encoding and decoding your text.
4+
The 'HillCipher' class below implements the Hill Cipher algorithm which uses
5+
modern linear algebra techniques to encode and decode text using an encryption
6+
key matrix.
87
98
Algorithm:
109
Let the order of the encryption key be N (as it is a square matrix).
@@ -24,12 +23,11 @@
2423
The determinant of the encryption key matrix must be relatively prime w.r.t 36.
2524
2625
Note:
27-
The algorithm implemented in this code considers only alphanumerics in the text.
28-
If the length of the text to be encrypted is not a multiple of the
29-
break key(the length of one batch of letters),the last character of the text
30-
is added to the text until the length of the text reaches a multiple of
31-
the break_key. So the text after decrypting might be a little different than
32-
the original text.
26+
This implementation only considers alphanumerics in the text. If the length of
27+
the text to be encrypted is not a multiple of the break key(the length of one
28+
batch of letters), the last character of the text is added to the text until the
29+
length of the text reaches a multiple of the break_key. So the text after
30+
decrypting might be a little different than the original text.
3331
3432
References:
3533
https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf
@@ -38,67 +36,66 @@
3836
3937
"""
4038

39+
import string
4140
import numpy
4241

4342

44-
def gcd(a: int, b: int) -> int:
43+
def greatest_common_divisor(a: int, b: int) -> int:
4544
"""
46-
>>> gcd(4, 8)
45+
>>> greatest_common_divisor(4, 8)
4746
4
48-
>>> gcd(8, 4)
47+
>>> greatest_common_divisor(8, 4)
4948
4
50-
>>> gcd(4, 7)
49+
>>> greatest_common_divisor(4, 7)
5150
1
52-
>>> gcd(0, 10)
51+
>>> greatest_common_divisor(0, 10)
5352
10
5453
"""
55-
if a == 0:
56-
return b
57-
return gcd(b % a, a)
54+
return b if a == 0 else greatest_common_divisor(b % a, a)
5855

5956

6057
class HillCipher:
61-
key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
58+
key_string = string.ascii_uppercase + string.digits
6259
# This cipher takes alphanumerics into account
6360
# i.e. a total of 36 characters
6461

6562
# take x and return x % len(key_string)
6663
modulus = numpy.vectorize(lambda x: x % 36)
6764

68-
toInt = numpy.vectorize(lambda x: round(x))
65+
to_int = numpy.vectorize(lambda x: round(x))
6966

7067
def __init__(self, encrypt_key):
7168
"""
72-
encrypt_key is an NxN numpy matrix
69+
encrypt_key is an NxN numpy array
7370
"""
7471
self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key
7572
self.check_determinant() # validate the determinant of the encryption key
7673
self.decrypt_key = None
7774
self.break_key = encrypt_key.shape[0]
7875

79-
def replaceLetters(self, letter: str) -> int:
76+
def replace_letters(self, letter: str) -> int:
8077
"""
81-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
82-
>>> hill_cipher.replaceLetters('T')
78+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
79+
>>> hill_cipher.replace_letters('T')
8380
19
84-
>>> hill_cipher.replaceLetters('0')
81+
>>> hill_cipher.replace_letters('0')
8582
26
8683
"""
8784
return self.key_string.index(letter)
8885

89-
def replaceNumbers(self, num: int) -> str:
86+
def replace_digits(self, num: int) -> str:
9087
"""
91-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
92-
>>> hill_cipher.replaceNumbers(19)
88+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
89+
>>> hill_cipher.replace_digits(19)
9390
'T'
94-
>>> hill_cipher.replaceNumbers(26)
91+
>>> hill_cipher.replace_digits(26)
9592
'0'
9693
"""
9794
return self.key_string[round(num)]
9895

9996
def check_determinant(self) -> None:
10097
"""
101-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
98+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
10299
>>> hill_cipher.check_determinant()
103100
"""
104101
det = round(numpy.linalg.det(self.encrypt_key))
@@ -107,19 +104,20 @@ def check_determinant(self) -> None:
107104
det = det % len(self.key_string)
108105

109106
req_l = len(self.key_string)
110-
if gcd(det, len(self.key_string)) != 1:
107+
if greatest_common_divisor(det, len(self.key_string)) != 1:
111108
raise ValueError(
112109
f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key."
113110
)
114111

115112
def process_text(self, text: str) -> str:
116113
"""
117-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
114+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
118115
>>> hill_cipher.process_text('Testing Hill Cipher')
119116
'TESTINGHILLCIPHERR'
117+
>>> hill_cipher.process_text('hello')
118+
'HELLOO'
120119
"""
121-
text = list(text.upper())
122-
chars = [char for char in text if char in self.key_string]
120+
chars = [char for char in text.upper() if char in self.key_string]
123121

124122
last = chars[-1]
125123
while len(chars) % self.break_key != 0:
@@ -129,33 +127,35 @@ def process_text(self, text: str) -> str:
129127

130128
def encrypt(self, text: str) -> str:
131129
"""
132-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
130+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
133131
>>> hill_cipher.encrypt('testing hill cipher')
134132
'WHXYJOLM9C6XT085LL'
133+
>>> hill_cipher.encrypt('hello')
134+
'85FF00'
135135
"""
136136
text = self.process_text(text.upper())
137137
encrypted = ""
138138

139139
for i in range(0, len(text) - self.break_key + 1, self.break_key):
140140
batch = text[i : i + self.break_key]
141-
batch_vec = [self.replaceLetters(char) for char in batch]
142-
batch_vec = numpy.matrix([batch_vec]).T
141+
batch_vec = [self.replace_letters(char) for char in batch]
142+
batch_vec = numpy.array([batch_vec]).T
143143
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[
144144
0
145145
]
146146
encrypted_batch = "".join(
147-
self.replaceNumbers(num) for num in batch_encrypted
147+
self.replace_digits(num) for num in batch_encrypted
148148
)
149149
encrypted += encrypted_batch
150150

151151
return encrypted
152152

153153
def make_decrypt_key(self):
154154
"""
155-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
155+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
156156
>>> hill_cipher.make_decrypt_key()
157-
matrix([[ 6., 25.],
158-
[ 5., 26.]])
157+
array([[ 6., 25.],
158+
[ 5., 26.]])
159159
"""
160160
det = round(numpy.linalg.det(self.encrypt_key))
161161

@@ -173,27 +173,29 @@ def make_decrypt_key(self):
173173
* numpy.linalg.inv(self.encrypt_key)
174174
)
175175

176-
return self.toInt(self.modulus(inv_key))
176+
return self.to_int(self.modulus(inv_key))
177177

178178
def decrypt(self, text: str) -> str:
179179
"""
180-
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
180+
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
181181
>>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
182182
'TESTINGHILLCIPHERR'
183+
>>> hill_cipher.decrypt('85FF00')
184+
'HELLOO'
183185
"""
184186
self.decrypt_key = self.make_decrypt_key()
185187
text = self.process_text(text.upper())
186188
decrypted = ""
187189

188190
for i in range(0, len(text) - self.break_key + 1, self.break_key):
189191
batch = text[i : i + self.break_key]
190-
batch_vec = [self.replaceLetters(char) for char in batch]
191-
batch_vec = numpy.matrix([batch_vec]).T
192+
batch_vec = [self.replace_letters(char) for char in batch]
193+
batch_vec = numpy.array([batch_vec]).T
192194
batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[
193195
0
194196
]
195197
decrypted_batch = "".join(
196-
self.replaceNumbers(num) for num in batch_decrypted
198+
self.replace_digits(num) for num in batch_decrypted
197199
)
198200
decrypted += decrypted_batch
199201

@@ -206,19 +208,13 @@ def main():
206208

207209
print("Enter each row of the encryption key with space separated integers")
208210
for i in range(N):
209-
row = list(map(int, input().split()))
211+
row = [int(x) for x in input().split()]
210212
hill_matrix.append(row)
211213

212-
hc = HillCipher(numpy.matrix(hill_matrix))
214+
hc = HillCipher(numpy.array(hill_matrix))
213215

214216
print("Would you like to encrypt or decrypt some text? (1 or 2)")
215-
option = input(
216-
"""
217-
1. Encrypt
218-
2. Decrypt
219-
"""
220-
)
221-
217+
option = input("\n1. Encrypt\n2. Decrypt\n")
222218
if option == "1":
223219
text_e = input("What text would you like to encrypt?: ")
224220
print("Your encrypted text is:")
@@ -231,6 +227,7 @@ def main():
231227

232228
if __name__ == "__main__":
233229
import doctest
230+
234231
doctest.testmod()
235232

236233
main()

0 commit comments

Comments
 (0)