Solved 2014 challenges 1 and 2
[cipher-tools.git] / cipher.py
index 5f39a780dd608d4385eb9db9d41c0c7b8edb4df7..c5d542bb915b4cfd0775d5c100e8dce46a271d88 100644 (file)
--- a/cipher.py
+++ b/cipher.py
@@ -1,6 +1,7 @@
 import string
 import collections
 import logging
 import string
 import collections
 import logging
+import math
 from itertools import zip_longest, cycle, chain
 from language_models import *
 
 from itertools import zip_longest, cycle, chain
 from language_models import *
 
@@ -18,18 +19,6 @@ for a in range(26):
         modular_division_table[b][c] = a
 
 
         modular_division_table[b][c] = a
 
 
-def ngrams(text, n):
-    """Returns all n-grams of a text
-    
-    >>> ngrams(sanitise('the quick brown fox'), 2) # doctest: +NORMALIZE_WHITESPACE
-    ['th', 'he', 'eq', 'qu', 'ui', 'ic', 'ck', 'kb', 'br', 'ro', 'ow', 'wn', 
-     'nf', 'fo', 'ox']
-    >>> ngrams(sanitise('the quick brown fox'), 4) # doctest: +NORMALIZE_WHITESPACE
-    ['theq', 'hequ', 'equi', 'quic', 'uick', 'ickb', 'ckbr', 'kbro', 'brow', 
-     'rown', 'ownf', 'wnfo', 'nfox']
-    """
-    return [text[i:i+n] for i in range(len(text)-n+1)]
-
 def every_nth(text, n, fillvalue=''):
     """Returns n strings, each of which consists of every nth character, 
     starting with the 0th, 1st, 2nd, ... (n-1)th character
 def every_nth(text, n, fillvalue=''):
     """Returns n strings, each of which consists of every nth character, 
     starting with the 0th, 1st, 2nd, ... (n-1)th character
@@ -324,46 +313,29 @@ def keyword_decipher(message, keyword, wrap_alphabet=0):
     cipher_translation = ''.maketrans(cipher_alphabet, string.ascii_lowercase)
     return message.lower().translate(cipher_translation)
 
     cipher_translation = ''.maketrans(cipher_alphabet, string.ascii_lowercase)
     return message.lower().translate(cipher_translation)
 
-def scytale_encipher(message, rows):
-    """Enciphers using the scytale transposition cipher.
-    Message is padded with spaces to allow all rows to be the same length.
 
 
-    >>> scytale_encipher('thequickbrownfox', 3)
-    'tcnhkfeboqrxuo iw '
-    >>> scytale_encipher('thequickbrownfox', 4)
-    'tubnhirfecooqkwx'
-    >>> scytale_encipher('thequickbrownfox', 5)
-    'tubn hirf ecoo qkwx '
-    >>> scytale_encipher('thequickbrownfox', 6)
-    'tqcrnxhukof eibwo '
-    >>> scytale_encipher('thequickbrownfox', 7)
-    'tqcrnx hukof  eibwo  '
+def vigenere_encipher(message, keyword):
+    """Vigenere encipher
+
+    >>> vigenere_encipher('hello', 'abc')
+    'hfnlp'
     """
     """
-    if len(message) % rows != 0:
-        message += ' '*(rows - len(message) % rows)
-    row_length = round(len(message) / rows)
-    slices = [message[i:i+row_length] 
-              for i in range(0, len(message), row_length)]
-    return ''.join([''.join(r) for r in zip_longest(*slices, fillvalue='')])
+    shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
+    pairs = zip(message, cycle(shifts))
+    return ''.join([caesar_encipher_letter(l, k) for l, k in pairs])
 
 
-def scytale_decipher(message, rows):
-    """Deciphers using the scytale transposition cipher.
-    Assumes the message is padded so that all rows are the same length.
-    
-    >>> scytale_decipher('tcnhkfeboqrxuo iw ', 3)
-    'thequickbrownfox  '
-    >>> scytale_decipher('tubnhirfecooqkwx', 4)
-    'thequickbrownfox'
-    >>> scytale_decipher('tubn hirf ecoo qkwx ', 5)
-    'thequickbrownfox    '
-    >>> scytale_decipher('tqcrnxhukof eibwo ', 6)
-    'thequickbrownfox  '
-    >>> scytale_decipher('tqcrnx hukof  eibwo  ', 7)
-    'thequickbrownfox     '
+def vigenere_decipher(message, keyword):
+    """Vigenere decipher
+
+    >>> vigenere_decipher('hfnlp', 'abc')
+    'hello'
     """
     """
-    cols = round(len(message) / rows)
-    columns = [message[i:i+rows] for i in range(0, cols * rows, rows)]
-    return ''.join([''.join(c) for c in zip_longest(*columns, fillvalue='')])
+    shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
+    pairs = zip(message, cycle(shifts))
+    return ''.join([caesar_decipher_letter(l, k) for l, k in pairs])
+
+beaufort_encipher=vigenere_decipher
+beaufort_decipher=vigenere_encipher
 
 
 def transpositions_of(keyword):
 
 
 def transpositions_of(keyword):
@@ -404,21 +376,44 @@ def column_transposition_encipher(message, keyword, fillvalue=' ',
       emptycolumnwise=False):
     """Enciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
       emptycolumnwise=False):
     """Enciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
+
+    >>> column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True)
+    'hlohr eltee '
+    >>> column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True, emptycolumnwise=True)
+    'hellothere  '
+    >>> column_transposition_encipher('hellothere', 'abcdef')
+    'hellothere  '
+    >>> column_transposition_encipher('hellothere', 'abcde')
+    'hellothere'
+    >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=True)
+    'hellothere'
+    >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=False)
+    'hlohreltee'
+    >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=True)
+    'htehlelroe'
+    >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=False)
+    'hellothere'
+    >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=True)
+    'heotllrehe'
+    >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=False)
+    'holrhetlee'
+    >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=True)
+    'htleehoelr'
+    >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=False)
+    'hleolteher'
+    >>> column_transposition_encipher('hellothere', 'cleverly')
+    'hleolthre e '
+    >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue='!')
+    'hleolthre!e!'
+    >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue=lambda: '*')
+    'hleolthre*e*'
     """
     """
-    # >>> column_transposition_encipher('hellothere', 'clever')
-    # 'hleolteher'
-    # >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue='!')
-    # 'hleolthre!e!'
-    # >>> column_transposition_encipher('hellothere', 'clever', columnwise=True)
-    # 'htleehoelr'
-    # """
     transpositions = transpositions_of(keyword)
     message += pad(len(message), len(transpositions), fillvalue)
     if fillcolumnwise:
     transpositions = transpositions_of(keyword)
     message += pad(len(message), len(transpositions), fillvalue)
     if fillcolumnwise:
-        rows = every_nth(message, len(transpositions))
+        rows = every_nth(message, len(message) // len(transpositions))
     else:
     else:
-        rows = chunks(mesage, len(transpositions))
-    columns = every_nth(message, len(transpositions), fillvalue=fillvalue)
+        rows = chunks(message, len(transpositions))
     transposed = [transpose(r, transpositions) for r in rows]
     if emptycolumnwise:
         return combine_every_nth(transposed)
     transposed = [transpose(r, transpositions) for r in rows]
     if emptycolumnwise:
         return combine_every_nth(transposed)
@@ -426,48 +421,77 @@ def column_transposition_encipher(message, keyword, fillvalue=' ',
         return ''.join(chain(*transposed))
 
 def column_transposition_decipher(message, keyword, fillvalue=' ', 
         return ''.join(chain(*transposed))
 
 def column_transposition_decipher(message, keyword, fillvalue=' ', 
-      columnwise=False):
+      fillcolumnwise=False,
+      emptycolumnwise=False):
     """Deciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
     """Deciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
+
+    >>> column_transposition_decipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=True)
+    'hellothere'
+    >>> column_transposition_decipher('hlohreltee', 'abcde', fillcolumnwise=True, emptycolumnwise=False)
+    'hellothere'
+    >>> column_transposition_decipher('htehlelroe', 'abcde', fillcolumnwise=False, emptycolumnwise=True)
+    'hellothere'
+    >>> column_transposition_decipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=False)
+    'hellothere'
+    >>> column_transposition_decipher('heotllrehe', 'clever', fillcolumnwise=True, emptycolumnwise=True)
+    'hellothere'
+    >>> column_transposition_decipher('holrhetlee', 'clever', fillcolumnwise=True, emptycolumnwise=False)
+    'hellothere'
+    >>> column_transposition_decipher('htleehoelr', 'clever', fillcolumnwise=False, emptycolumnwise=True)
+    'hellothere'
+    >>> column_transposition_decipher('hleolteher', 'clever', fillcolumnwise=False, emptycolumnwise=False)
+    'hellothere'
     """
     """
-    # >>> column_transposition_decipher('hleolteher', 'clever')
-    # 'hellothere'
-    # >>> column_transposition_decipher('hleolthre!e!', 'cleverly', fillvalue='?')
-    # 'hellothere!!'
-    # >>> column_transposition_decipher('htleehoelr', 'clever', columnwise=True)
-    # 'hellothere'
-    # """
     transpositions = transpositions_of(keyword)
     transpositions = transpositions_of(keyword)
-    if columnwise:
-        columns = chunks(message, int(len(message) / len(transpositions)))
+    message += pad(len(message), len(transpositions), '*')
+    if emptycolumnwise:
+        rows = every_nth(message, len(message) // len(transpositions))
     else:
     else:
-        columns = every_nth(message, len(transpositions), fillvalue=fillvalue)
-    untransposed_columns = untranspose(columns, transpositions)
-    return combine_every_nth(untransposed_columns)
-
+        rows = chunks(message, len(transpositions))
+    untransposed = [untranspose(r, transpositions) for r in rows]
+    if fillcolumnwise:
+        return combine_every_nth(untransposed)
+    else:
+        return ''.join(chain(*untransposed))
 
 
-def vigenere_encipher(message, keyword):
-    """Vigenere encipher
+def scytale_encipher(message, rows, fillvalue=' '):
+    """Enciphers using the scytale transposition cipher.
+    Message is padded with spaces to allow all rows to be the same length.
 
 
-    >>> vigenere_encipher('hello', 'abc')
-    'hfnlp'
+    >>> scytale_encipher('thequickbrownfox', 3)
+    'tcnhkfeboqrxuo iw '
+    >>> scytale_encipher('thequickbrownfox', 4)
+    'tubnhirfecooqkwx'
+    >>> scytale_encipher('thequickbrownfox', 5)
+    'tubnhirfecooqkwx'
+    >>> scytale_encipher('thequickbrownfox', 6)
+    'tqcrnxhukof eibwo '
+    >>> scytale_encipher('thequickbrownfox', 7)
+    'tqcrnxhukof eibwo '
     """
     """
-    shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
-    pairs = zip(message, cycle(shifts))
-    return ''.join([caesar_encipher_letter(l, k) for l, k in pairs])
+    transpositions = [i for i in range(math.ceil(len(message) / rows))]
+    return column_transposition_encipher(message, transpositions, 
+        fillcolumnwise=False, emptycolumnwise=True)
 
 
-def vigenere_decipher(message, keyword):
-    """Vigenere decipher
-
-    >>> vigenere_decipher('hfnlp', 'abc')
-    'hello'
+def scytale_decipher(message, rows):
+    """Deciphers using the scytale transposition cipher.
+    Assumes the message is padded so that all rows are the same length.
+    
+    >>> scytale_decipher('tcnhkfeboqrxuo iw ', 3)
+    'thequickbrownfox  '
+    >>> scytale_decipher('tubnhirfecooqkwx', 4)
+    'thequickbrownfox'
+    >>> scytale_decipher('tubnhirfecooqkwx', 5)
+    'thequickbrownfox'
+    >>> scytale_decipher('tqcrnxhukof eibwo ', 6)
+    'thequickbrownfox  '
+    >>> scytale_decipher('tqcrnxhukof eibwo ', 7)
+    'thequickbrownfox  '
     """
     """
-    shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
-    pairs = zip(message, cycle(shifts))
-    return ''.join([caesar_decipher_letter(l, k) for l, k in pairs])
-
-beaufort_encipher=vigenere_decipher
-beaufort_decipher=vigenere_encipher
+    transpositions = [i for i in range(math.ceil(len(message) / rows))]
+    return column_transposition_decipher(message, transpositions, 
+        fillcolumnwise=False, emptycolumnwise=True)
 
 
 if __name__ == "__main__":
 
 
 if __name__ == "__main__":