Skip to content

Commit aa120ce

Browse files
bharath-kothapoyea
andauthored
Add tests and type hints to hill cipher (TheAlgorithms#1991)
* Added tests and type hints to hill cipher * Remove extra >>> * import doctest Co-authored-by: John Law <johnlaw.po@gmail.com>
1 parent bc8e8f0 commit aa120ce

File tree

1 file changed

+77
-20
lines changed

1 file changed

+77
-20
lines changed

ciphers/hill_cipher.py

+77-20
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,17 @@
4141
import numpy
4242

4343

44-
def gcd(a, b):
44+
def gcd(a: int, b: int) -> int:
45+
"""
46+
>>> gcd(4, 8)
47+
4
48+
>>> gcd(8, 4)
49+
4
50+
>>> gcd(4, 7)
51+
1
52+
>>> gcd(0, 10)
53+
10
54+
"""
4555
if a == 0:
4656
return b
4757
return gcd(b % a, a)
@@ -52,9 +62,6 @@ class HillCipher:
5262
# This cipher takes alphanumerics into account
5363
# i.e. a total of 36 characters
5464

55-
replaceLetters = lambda self, letter: self.key_string.index(letter)
56-
replaceNumbers = lambda self, num: self.key_string[round(num)]
57-
5865
# take x and return x % len(key_string)
5966
modulus = numpy.vectorize(lambda x: x % 36)
6067

@@ -69,7 +76,31 @@ def __init__(self, encrypt_key):
6976
self.decrypt_key = None
7077
self.break_key = encrypt_key.shape[0]
7178

72-
def check_determinant(self):
79+
def replaceLetters(self, letter: str) -> int:
80+
"""
81+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
82+
>>> hill_cipher.replaceLetters('T')
83+
19
84+
>>> hill_cipher.replaceLetters('0')
85+
26
86+
"""
87+
return self.key_string.index(letter)
88+
89+
def replaceNumbers(self, num: int) -> str:
90+
"""
91+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
92+
>>> hill_cipher.replaceNumbers(19)
93+
'T'
94+
>>> hill_cipher.replaceNumbers(26)
95+
'0'
96+
"""
97+
return self.key_string[round(num)]
98+
99+
def check_determinant(self) -> None:
100+
"""
101+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
102+
>>> hill_cipher.check_determinant()
103+
"""
73104
det = round(numpy.linalg.det(self.encrypt_key))
74105

75106
if det < 0:
@@ -78,38 +109,54 @@ def check_determinant(self):
78109
req_l = len(self.key_string)
79110
if gcd(det, len(self.key_string)) != 1:
80111
raise ValueError(
81-
"discriminant modular {} of encryption key({}) is not co prime w.r.t {}.\nTry another key.".format(
82-
req_l, det, req_l
83-
)
112+
f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key."
84113
)
85114

86-
def process_text(self, text):
115+
def process_text(self, text: str) -> str:
116+
"""
117+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
118+
>>> hill_cipher.process_text('Testing Hill Cipher')
119+
'TESTINGHILLCIPHERR'
120+
"""
87121
text = list(text.upper())
88-
text = [char for char in text if char in self.key_string]
122+
chars = [char for char in text if char in self.key_string]
89123

90-
last = text[-1]
91-
while len(text) % self.break_key != 0:
92-
text.append(last)
124+
last = chars[-1]
125+
while len(chars) % self.break_key != 0:
126+
chars.append(last)
93127

94-
return "".join(text)
128+
return "".join(chars)
95129

96-
def encrypt(self, text):
130+
def encrypt(self, text: str) -> str:
131+
"""
132+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
133+
>>> hill_cipher.encrypt('testing hill cipher')
134+
'WHXYJOLM9C6XT085LL'
135+
"""
97136
text = self.process_text(text.upper())
98137
encrypted = ""
99138

100139
for i in range(0, len(text) - self.break_key + 1, self.break_key):
101140
batch = text[i : i + self.break_key]
102-
batch_vec = list(map(self.replaceLetters, batch))
141+
batch_vec = [self.replaceLetters(char) for char in batch]
103142
batch_vec = numpy.matrix([batch_vec]).T
104143
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[
105144
0
106145
]
107-
encrypted_batch = "".join(list(map(self.replaceNumbers, batch_encrypted)))
146+
encrypted_batch = "".join(
147+
self.replaceNumbers(num) for num in batch_encrypted
148+
)
108149
encrypted += encrypted_batch
109150

110151
return encrypted
111152

112153
def make_decrypt_key(self):
154+
"""
155+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
156+
>>> hill_cipher.make_decrypt_key()
157+
matrix([[ 6., 25.],
158+
[ 5., 26.]])
159+
"""
113160
det = round(numpy.linalg.det(self.encrypt_key))
114161

115162
if det < 0:
@@ -128,19 +175,26 @@ def make_decrypt_key(self):
128175

129176
return self.toInt(self.modulus(inv_key))
130177

131-
def decrypt(self, text):
178+
def decrypt(self, text: str) -> str:
179+
"""
180+
>>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]]))
181+
>>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
182+
'TESTINGHILLCIPHERR'
183+
"""
132184
self.decrypt_key = self.make_decrypt_key()
133185
text = self.process_text(text.upper())
134186
decrypted = ""
135187

136188
for i in range(0, len(text) - self.break_key + 1, self.break_key):
137189
batch = text[i : i + self.break_key]
138-
batch_vec = list(map(self.replaceLetters, batch))
190+
batch_vec = [self.replaceLetters(char) for char in batch]
139191
batch_vec = numpy.matrix([batch_vec]).T
140192
batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[
141193
0
142194
]
143-
decrypted_batch = "".join(list(map(self.replaceNumbers, batch_decrypted)))
195+
decrypted_batch = "".join(
196+
self.replaceNumbers(num) for num in batch_decrypted
197+
)
144198
decrypted += decrypted_batch
145199

146200
return decrypted
@@ -176,4 +230,7 @@ def main():
176230

177231

178232
if __name__ == "__main__":
233+
import doctest
234+
doctest.testmod()
235+
179236
main()

0 commit comments

Comments
 (0)