Skip to content

Commit e0b2a1f

Browse files
authored
Fix: cleaned caesar_cipher.py with error handling and better docs
1 parent e2a78d4 commit e0b2a1f

File tree

1 file changed

+77
-223
lines changed

1 file changed

+77
-223
lines changed

ciphers/caesar_cipher.py

Lines changed: 77 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -1,256 +1,110 @@
11
from __future__ import annotations
2-
32
from string import ascii_letters
43

54

65
def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
76
"""
8-
encrypt
9-
=======
10-
11-
Encodes a given string with the caesar cipher and returns the encoded
12-
message
13-
14-
Parameters:
15-
-----------
16-
17-
* `input_string`: the plain-text that needs to be encoded
18-
* `key`: the number of letters to shift the message by
19-
20-
Optional:
21-
22-
* `alphabet` (``None``): the alphabet used to encode the cipher, if not
23-
specified, the standard english alphabet with upper and lowercase
24-
letters is used
25-
26-
Returns:
27-
28-
* A string containing the encoded cipher-text
29-
30-
More on the caesar cipher
31-
=========================
32-
33-
The caesar cipher is named after Julius Caesar who used it when sending
34-
secret military messages to his troops. This is a simple substitution cipher
35-
where every character in the plain-text is shifted by a certain number known
36-
as the "key" or "shift".
37-
38-
Example:
39-
Say we have the following message:
40-
``Hello, captain``
41-
42-
And our alphabet is made up of lower and uppercase letters:
43-
``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
44-
45-
And our shift is ``2``
46-
47-
We can then encode the message, one letter at a time. ``H`` would become ``J``,
48-
since ``J`` is two letters away, and so on. If the shift is ever two large, or
49-
our letter is at the end of the alphabet, we just start at the beginning
50-
(``Z`` would shift to ``a`` then ``b`` and so on).
51-
52-
Our final message would be ``Jgnnq, ecrvckp``
53-
54-
Further reading
55-
===============
56-
57-
* https://en.m.wikipedia.org/wiki/Caesar_cipher
58-
59-
Doctests
60-
========
61-
7+
Encrypts a given string with the Caesar cipher and returns the encoded message.
8+
9+
Parameters
10+
----------
11+
input_string : str
12+
The plain-text that needs to be encoded.
13+
key : int
14+
The number of letters to shift the message by.
15+
alphabet : str | None, optional
16+
The alphabet used to encode the cipher.
17+
If not specified, the standard English alphabet (a-z, A-Z) is used.
18+
19+
Returns
20+
-------
21+
str
22+
A string containing the encoded cipher-text.
23+
24+
Raises
25+
------
26+
TypeError
27+
If input_string is not a string or key is not an integer.
28+
29+
Examples
30+
--------
6231
>>> encrypt('The quick brown fox jumps over the lazy dog', 8)
63-
'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo'
64-
65-
>>> encrypt('A very large key', 8000)
66-
's nWjq dSjYW cWq'
67-
32+
'Bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo'
6833
>>> encrypt('a lowercase alphabet', 5, 'abcdefghijklmnopqrstuvwxyz')
6934
'f qtbjwhfxj fqumfgjy'
7035
"""
71-
# Set default alphabet to lower and upper case english chars
72-
alpha = alphabet or ascii_letters
36+
if not isinstance(input_string, str):
37+
raise TypeError("input_string must be a string.")
38+
if not isinstance(key, int):
39+
raise TypeError("key must be an integer.")
7340

74-
# The final result string
75-
result = ""
41+
alpha = alphabet or ascii_letters
42+
result = []
7643

7744
for character in input_string:
7845
if character not in alpha:
79-
# Append without encryption if character is not in the alphabet
80-
result += character
46+
result.append(character)
8147
else:
82-
# Get the index of the new key and make sure it isn't too large
83-
new_key = (alpha.index(character) + key) % len(alpha)
84-
85-
# Append the encoded character to the alphabet
86-
result += alpha[new_key]
48+
new_index = (alpha.index(character) + key) % len(alpha)
49+
result.append(alpha[new_index])
8750

88-
return result
51+
return "".join(result)
8952

9053

9154
def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
9255
"""
93-
decrypt
94-
=======
95-
96-
Decodes a given string of cipher-text and returns the decoded plain-text
97-
98-
Parameters:
99-
-----------
100-
101-
* `input_string`: the cipher-text that needs to be decoded
102-
* `key`: the number of letters to shift the message backwards by to decode
103-
104-
Optional:
105-
106-
* `alphabet` (``None``): the alphabet used to decode the cipher, if not
107-
specified, the standard english alphabet with upper and lowercase
108-
letters is used
109-
110-
Returns:
111-
112-
* A string containing the decoded plain-text
113-
114-
More on the caesar cipher
115-
=========================
116-
117-
The caesar cipher is named after Julius Caesar who used it when sending
118-
secret military messages to his troops. This is a simple substitution cipher
119-
where very character in the plain-text is shifted by a certain number known
120-
as the "key" or "shift". Please keep in mind, here we will be focused on
121-
decryption.
122-
123-
Example:
124-
Say we have the following cipher-text:
125-
``Jgnnq, ecrvckp``
126-
127-
And our alphabet is made up of lower and uppercase letters:
128-
``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
129-
130-
And our shift is ``2``
131-
132-
To decode the message, we would do the same thing as encoding, but in
133-
reverse. The first letter, ``J`` would become ``H`` (remember: we are decoding)
134-
because ``H`` is two letters in reverse (to the left) of ``J``. We would
135-
continue doing this. A letter like ``a`` would shift back to the end of
136-
the alphabet, and would become ``Z`` or ``Y`` and so on.
137-
138-
Our final message would be ``Hello, captain``
139-
140-
Further reading
141-
===============
142-
143-
* https://en.m.wikipedia.org/wiki/Caesar_cipher
144-
145-
Doctests
146-
========
147-
148-
>>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8)
56+
Decodes a Caesar cipher text using the provided key.
57+
58+
Parameters
59+
----------
60+
input_string : str
61+
The cipher-text that needs to be decoded.
62+
key : int
63+
The number of letters to shift backward.
64+
alphabet : str | None, optional
65+
The alphabet used to decode the cipher.
66+
67+
Returns
68+
-------
69+
str
70+
The decoded plain-text.
71+
72+
Examples
73+
--------
74+
>>> decrypt('Bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8)
14975
'The quick brown fox jumps over the lazy dog'
150-
151-
>>> decrypt('s nWjq dSjYW cWq', 8000)
152-
'A very large key'
153-
154-
>>> decrypt('f qtbjwhfxj fqumfgjy', 5, 'abcdefghijklmnopqrstuvwxyz')
155-
'a lowercase alphabet'
15676
"""
157-
# Turn on decode mode by making the key negative
158-
key *= -1
159-
160-
return encrypt(input_string, key, alphabet)
77+
return encrypt(input_string, -key, alphabet)
16178

16279

16380
def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str]:
16481
"""
165-
brute_force
166-
===========
167-
168-
Returns all the possible combinations of keys and the decoded strings in the
169-
form of a dictionary
170-
171-
Parameters:
172-
-----------
173-
174-
* `input_string`: the cipher-text that needs to be used during brute-force
175-
176-
Optional:
177-
178-
* `alphabet` (``None``): the alphabet used to decode the cipher, if not
179-
specified, the standard english alphabet with upper and lowercase
180-
letters is used
181-
182-
More about brute force
183-
======================
184-
185-
Brute force is when a person intercepts a message or password, not knowing
186-
the key and tries every single combination. This is easy with the caesar
187-
cipher since there are only all the letters in the alphabet. The more
188-
complex the cipher, the larger amount of time it will take to do brute force
189-
190-
Ex:
191-
Say we have a ``5`` letter alphabet (``abcde``), for simplicity and we intercepted
192-
the following message: ``dbc``,
193-
we could then just write out every combination:
194-
``ecd``... and so on, until we reach a combination that makes sense:
195-
``cab``
196-
197-
Further reading
198-
===============
199-
200-
* https://en.wikipedia.org/wiki/Brute_force
201-
202-
Doctests
203-
========
204-
82+
Attempts to brute-force all possible Caesar cipher keys.
83+
84+
Parameters
85+
----------
86+
input_string : str
87+
The cipher-text to attempt decoding.
88+
alphabet : str | None, optional
89+
The alphabet used to decode the cipher.
90+
91+
Returns
92+
-------
93+
dict[int, str]
94+
A dictionary mapping each key to its decoded message.
95+
96+
Examples
97+
--------
20598
>>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20]
20699
"Please don't brute force me!"
207-
208-
>>> brute_force(1)
209-
Traceback (most recent call last):
210-
TypeError: 'int' object is not iterable
211100
"""
212-
# Set default alphabet to lower and upper case english chars
213-
alpha = alphabet or ascii_letters
101+
if not isinstance(input_string, str):
102+
raise TypeError("input_string must be a string.")
214103

215-
# To store data on all the combinations
216-
brute_force_data = {}
104+
alpha = alphabet or ascii_letters
105+
results = {}
217106

218-
# Cycle through each combination
219107
for key in range(1, len(alpha) + 1):
220-
# Decrypt the message and store the result in the data
221-
brute_force_data[key] = decrypt(input_string, key, alpha)
222-
223-
return brute_force_data
224-
225-
226-
if __name__ == "__main__":
227-
while True:
228-
print(f"\n{'-' * 10}\n Menu\n{'-' * 10}")
229-
print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n")
230-
231-
# get user input
232-
choice = input("\nWhat would you like to do?: ").strip() or "4"
233-
234-
# run functions based on what the user chose
235-
if choice not in ("1", "2", "3", "4"):
236-
print("Invalid choice, please enter a valid choice")
237-
elif choice == "1":
238-
input_string = input("Please enter the string to be encrypted: ")
239-
key = int(input("Please enter off-set: ").strip())
240-
241-
print(encrypt(input_string, key))
242-
elif choice == "2":
243-
input_string = input("Please enter the string to be decrypted: ")
244-
key = int(input("Please enter off-set: ").strip())
245-
246-
print(decrypt(input_string, key))
247-
elif choice == "3":
248-
input_string = input("Please enter the string to be decrypted: ")
249-
brute_force_data = brute_force(input_string)
250-
251-
for key, value in brute_force_data.items():
252-
print(f"Key: {key} | Message: {value}")
108+
results[key] = decrypt(input_string, key, alpha)
253109

254-
elif choice == "4":
255-
print("Goodbye.")
256-
break
110+
return results

0 commit comments

Comments
 (0)