Merge branch 'development' into presentation-slides
[cipher-training.git] / cipher.py
index 2386ae802f35d7e274fc7719827212362b5d032f..ba62d411a902e40f6bc1b259d2733111f891407d 100644 (file)
--- a/cipher.py
+++ b/cipher.py
@@ -1,16 +1,10 @@
 import string
 import collections
-import logging
 import math
+from enum import Enum
 from itertools import zip_longest, cycle, chain
 from language_models import *
 
-logger = logging.getLogger(__name__)
-logger.addHandler(logging.FileHandler('cipher.log'))
-logger.setLevel(logging.WARNING)
-#logger.setLevel(logging.INFO)
-#logger.setLevel(logging.DEBUG)
-
 
 modular_division_table = [[0]*26 for _ in range(26)]
 for a in range(26):
@@ -99,7 +93,7 @@ def deduplicate(text):
     return list(collections.OrderedDict.fromkeys(text))
 
 
-def caesar_encipher_letter(letter, shift):
+def caesar_encipher_letter(accented_letter, shift):
     """Encipher a letter, given a shift amount
 
     >>> caesar_encipher_letter('a', 1)
@@ -118,7 +112,12 @@ def caesar_encipher_letter(letter, shift):
     'y'
     >>> caesar_encipher_letter('a', -1)
     'z'
+    >>> caesar_encipher_letter('A', 1)
+    'B'
+    >>> caesar_encipher_letter('é', 1)
+    'f'
     """
+    letter = unaccent(accented_letter)
     if letter in string.ascii_letters:
         if letter in string.ascii_uppercase:
             alphabet_start = ord('A')
@@ -150,6 +149,8 @@ def caesar_encipher(message, shift):
     'cdezab'
     >>> caesar_encipher('ab cx yz', 2)
     'cd ez ab'
+    >>> caesar_encipher('Héllo World!', 2)
+    'Jgnnq Yqtnf!'
     """
     enciphered = [caesar_encipher_letter(l, shift) for l in message]
     return ''.join(enciphered)
@@ -163,10 +164,12 @@ def caesar_decipher(message, shift):
     'abc'
     >>> caesar_decipher('cd ez ab', 2)
     'ab cx yz'
+    >>> caesar_decipher('Jgnnq Yqtnf!', 2)
+    'Hello World!'
     """
     return caesar_encipher(message, -shift)
 
-def affine_encipher_letter(letter, multiplier=1, adder=0, one_based=True):
+def affine_encipher_letter(accented_letter, multiplier=1, adder=0, one_based=True):
     """Encipher a letter, given a multiplier and adder
     
     >>> ''.join([affine_encipher_letter(l, 3, 5, True) \
@@ -176,6 +179,7 @@ def affine_encipher_letter(letter, multiplier=1, adder=0, one_based=True):
             for l in string.ascii_uppercase])
     'FILORUXADGJMPSVYBEHKNQTWZC'
     """
+    letter = unaccent(accented_letter)
     if letter in string.ascii_letters:
         if letter in string.ascii_uppercase:
             alphabet_start = ord('A')
@@ -237,28 +241,31 @@ def affine_decipher(message, multiplier=1, adder=0, one_based=True):
     return ''.join(enciphered)
 
 
-def keyword_cipher_alphabet_of(keyword, wrap_alphabet=0):
+class Keyword_wrap_alphabet(Enum):
+    from_a = 0
+    from_last = 1
+    from_largest = 2
+
+
+def keyword_cipher_alphabet_of(keyword, wrap_alphabet=Keyword_wrap_alphabet.from_a):
     """Find the cipher alphabet given a keyword.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
-    0 : from 'a'
-    1 : from the last letter in the sanitised keyword
-    2 : from the largest letter in the sanitised keyword
 
     >>> keyword_cipher_alphabet_of('bayes')
     'bayescdfghijklmnopqrtuvwxz'
-    >>> keyword_cipher_alphabet_of('bayes', 0)
+    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_a)
     'bayescdfghijklmnopqrtuvwxz'
-    >>> keyword_cipher_alphabet_of('bayes', 1)
+    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_last)
     'bayestuvwxzcdfghijklmnopqr'
-    >>> keyword_cipher_alphabet_of('bayes', 2)
+    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_largest)
     'bayeszcdfghijklmnopqrtuvwx'
     """
-    if wrap_alphabet == 0:
+    if wrap_alphabet == Keyword_wrap_alphabet.from_a:
         cipher_alphabet = ''.join(deduplicate(sanitise(keyword) + 
                                               string.ascii_lowercase))
     else:
-        if wrap_alphabet == 1:
+        if wrap_alphabet == Keyword_wrap_alphabet.from_last:
             last_keyword_letter = deduplicate(sanitise(keyword))[-1]
         else:
             last_keyword_letter = sorted(sanitise(keyword))[-1]
@@ -271,7 +278,7 @@ def keyword_cipher_alphabet_of(keyword, wrap_alphabet=0):
     return cipher_alphabet
 
 
-def keyword_encipher(message, keyword, wrap_alphabet=0):
+def keyword_encipher(message, keyword, wrap_alphabet=Keyword_wrap_alphabet.from_a):
     """Enciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
@@ -281,18 +288,18 @@ def keyword_encipher(message, keyword, wrap_alphabet=0):
 
     >>> keyword_encipher('test message', 'bayes')
     'rsqr ksqqbds'
-    >>> keyword_encipher('test message', 'bayes', 0)
+    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_a)
     'rsqr ksqqbds'
-    >>> keyword_encipher('test message', 'bayes', 1)
+    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_last)
     'lskl dskkbus'
-    >>> keyword_encipher('test message', 'bayes', 2)
+    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_largest)
     'qspq jsppbcs'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
     cipher_translation = ''.maketrans(string.ascii_lowercase, cipher_alphabet)
-    return message.lower().translate(cipher_translation)
+    return unaccent(message).lower().translate(cipher_translation)
 
-def keyword_decipher(message, keyword, wrap_alphabet=0):
+def keyword_decipher(message, keyword, wrap_alphabet=Keyword_wrap_alphabet.from_a):
     """Deciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
@@ -302,11 +309,11 @@ def keyword_decipher(message, keyword, wrap_alphabet=0):
     
     >>> keyword_decipher('rsqr ksqqbds', 'bayes')
     'test message'
-    >>> keyword_decipher('rsqr ksqqbds', 'bayes', 0)
+    >>> keyword_decipher('rsqr ksqqbds', 'bayes', Keyword_wrap_alphabet.from_a)
     'test message'
-    >>> keyword_decipher('lskl dskkbus', 'bayes', 1)
+    >>> keyword_decipher('lskl dskkbus', 'bayes', Keyword_wrap_alphabet.from_last)
     'test message'
-    >>> keyword_decipher('qspq jsppbcs', 'bayes', 2)                                                                                            
+    >>> keyword_decipher('qspq jsppbcs', 'bayes', Keyword_wrap_alphabet.from_largest)
     'test message'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)