Minor documentation updates
[szyfrow.git] / szyfrow / amsco.py
index 48ab30627b7c46f472b526a2fcbd4cfbe3c05b6c..3372569a3ae6a1ef1b2b04602054870f37d9f72b 100644 (file)
@@ -1,22 +1,60 @@
+"""Enciphering and deciphering using the [Amsco cipher](http://ericbrandel.com/2016/10/09/the-amsco-cipher/). 
+Also attempts to break messages that use an Amsco cipher.
+
+The Amsco cipher is a column transpositoin cipher. The plaintext is laid out, 
+row by row, into columns. However, different numbers of letters are laid out
+in each cell, typically in a 1-2 pattern.
+
+It's clearer with an example. Consider we're using the keyword "perceptive", 
+which turns into "perctiv". The text ""It is a truth universally 
+acknowledged, that a single man in, possession of a good fortune, must be in 
+want of a wife." is laid out in seven columns like this:
+
+    p  e  r  c  t  i  v
+    --------------------
+    i  ti s  at r  ut h 
+    un i  ve r  sa l  ly 
+    a  ck n  ow l  ed g 
+    ed t  ha t  as i  ng 
+    l  em a  ni n  po s 
+    se s  si o  no f  ag 
+    o  od f  or t  un e 
+    mu s  tb e  in w  an 
+    t  of a  wi f  e
+
+The ciphertext is read out in columns, according to the order of the keyword.
+In this example, the "c" column is read first, then the "e" column, and so on.
+That gives the ciphertext of "atrowtnioorewi tiicktemsodsof utledipofunwe 
+iunaedlseomut svenhaasiftba rsalasnnotinf hlygngsagean".
+"""
+
 from enum import Enum
 import multiprocessing 
 import itertools
 
 from szyfrow.support.utilities import *
 from szyfrow.support.language_models import *
 from enum import Enum
 import multiprocessing 
 import itertools
 
 from szyfrow.support.utilities import *
 from szyfrow.support.language_models import *
-from szyfrow.column_transposition import transpositions, transpositions_of
 
 
-# Where each piece of text ends up in the AMSCO transpositon cipher.
-# 'index' shows where the slice appears in the plaintext, with the slice
-# from 'start' to 'end'
+__pdoc__ = {}
+
 AmscoSlice = collections.namedtuple('AmscoSlice', ['index', 'start', 'end'])
 AmscoSlice = collections.namedtuple('AmscoSlice', ['index', 'start', 'end'])
+__pdoc__['AmscoSlice'] = """Where each piece of plainatext ends up in the AMSCO 
+transpositon cipher."""
+__pdoc__['AmscoSlice.index'] = """Where the slice appears in the plaintext"""
+__pdoc__['AmscoSlice.start'] = """Where the slice starts in the plaintext"""
+__pdoc__['AmscoSlice.end'] = """Where the slice ends in the plaintext"""
 
 class AmscoFillStyle(Enum):
 
 class AmscoFillStyle(Enum):
+    """Different methods of filling the grid.
+    * `continuous`: continue the fillpattern unbroken by row boundaries
+    * `same_each_row`: each row has the same fillpattern
+    * `reverse_each_row`: each row has the reversed fillpattern to the row above
+    """
     continuous = 1
     same_each_row = 2
     reverse_each_row = 3
 
     continuous = 1
     same_each_row = 2
     reverse_each_row = 3
 
-def amsco_transposition_positions(message, keyword, 
+def amsco_positions(message, keyword, 
       fillpattern=(1, 2),
       fillstyle=AmscoFillStyle.continuous,
       fillcolumnwise=False,
       fillpattern=(1, 2),
       fillstyle=AmscoFillStyle.continuous,
       fillcolumnwise=False,
@@ -25,7 +63,7 @@ def amsco_transposition_positions(message, keyword,
     grid shows the index of that slice and the start and end positions of the
     plaintext that go to make it up.
 
     grid shows the index of that slice and the start and end positions of the
     plaintext that go to make it up.
 
-    >>> amsco_transposition_positions(string.ascii_lowercase, 'freddy', \
+    >>> amsco_positions(string.ascii_lowercase, 'freddy', \
         fillpattern=(1, 2)) # doctest:  +NORMALIZE_WHITESPACE
     [[AmscoSlice(index=3, start=4, end=6),
      AmscoSlice(index=2, start=3, end=4),
         fillpattern=(1, 2)) # doctest:  +NORMALIZE_WHITESPACE
     [[AmscoSlice(index=3, start=4, end=6),
      AmscoSlice(index=2, start=3, end=4),
@@ -72,64 +110,64 @@ def amsco_transposition_positions(message, keyword,
             current_fillpattern = list(reversed(current_fillpattern))
     return [transpose(r, transpositions) for r in grid]
 
             current_fillpattern = list(reversed(current_fillpattern))
     return [transpose(r, transpositions) for r in grid]
 
-def amsco_transposition_encipher(message, keyword, 
+def amsco_encipher(message, keyword, 
     fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
     """AMSCO transposition encipher.
 
     fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
     """AMSCO transposition encipher.
 
-    >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(1, 2))
+    >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2))
     'hoteelhler'
     'hoteelhler'
-    >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(2, 1))
+    >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1))
     'hetelhelor'
     'hetelhelor'
-    >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(1, 2))
+    >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2))
     'hotelerelh'
     'hotelerelh'
-    >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(2, 1))
+    >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1))
     'hetelorlhe'
     'hetelorlhe'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'encode')
+    >>> amsco_encipher('hereissometexttoencipher', 'encode')
     'etecstthhomoerereenisxip'
     'etecstthhomoerereenisxip'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
+    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
     'hetcsoeisterereipexthomn'
     'hetcsoeisterereipexthomn'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
     'hecsoisttererteipexhomen'
     'hecsoisttererteipexhomen'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
+    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
     'heecisoosttrrtepeixhemen'
     'heecisoosttrrtepeixhemen'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
+    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
     'hxtomephescieretoeisnter'
     'hxtomephescieretoeisnter'
-    >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
     'hxomeiphscerettoisenteer'
     """
     'hxomeiphscerettoisenteer'
     """
-    grid = amsco_transposition_positions(message, keyword, 
+    grid = amsco_positions(message, keyword, 
         fillpattern=fillpattern, fillstyle=fillstyle)
     ct_as_grid = [[message[s.start:s.end] for s in r] for r in grid]
     return combine_every_nth(ct_as_grid)
 
 
         fillpattern=fillpattern, fillstyle=fillstyle)
     ct_as_grid = [[message[s.start:s.end] for s in r] for r in grid]
     return combine_every_nth(ct_as_grid)
 
 
-def amsco_transposition_decipher(message, keyword, 
+def amsco_decipher(message, keyword, 
     fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
     """AMSCO transposition decipher
 
     fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
     """AMSCO transposition decipher
 
-    >>> amsco_transposition_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
+    >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
     'hellothere'
     'hellothere'
-    >>> amsco_transposition_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
+    >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
     'hellothere'
     'hellothere'
-    >>> amsco_transposition_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
+    >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
     'hellothere'
     'hellothere'
-    >>> amsco_transposition_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
+    >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
     'hellothere'
     'hellothere'
-    >>> amsco_transposition_decipher('etecstthhomoerereenisxip', 'encode')
+    >>> amsco_decipher('etecstthhomoerereenisxip', 'encode')
     'hereissometexttoencipher'
     'hereissometexttoencipher'
-    >>> amsco_transposition_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
+    >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
     'hereissometexttoencipher'
     'hereissometexttoencipher'
-    >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+    >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
     'hereissometexttoencipher'
     'hereissometexttoencipher'
-    >>> amsco_transposition_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
+    >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
     'hereissometexttoencipher'
     'hereissometexttoencipher'
-    >>> amsco_transposition_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
+    >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
     'hereissometexttoencipher'
     'hereissometexttoencipher'
-    >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+    >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
     'hereissometexttoencipher'
     """
 
     'hereissometexttoencipher'
     """
 
-    grid = amsco_transposition_positions(message, keyword, 
+    grid = amsco_positions(message, keyword, 
         fillpattern=fillpattern, fillstyle=fillstyle)
     transposed_sections = [s for c in [l for l in zip(*grid)] for s in c]
     plaintext_list = [''] * len(transposed_sections)
         fillpattern=fillpattern, fillstyle=fillstyle)
     transposed_sections = [s for c in [l for l in zip(*grid)] for s in c]
     plaintext_list = [''] * len(transposed_sections)
@@ -140,16 +178,19 @@ def amsco_transposition_decipher(message, keyword,
     return cat(plaintext_list)
 
 
     return cat(plaintext_list)
 
 
-def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)],
+def amsco_break(message, translist=None, patterns = [(1, 2), (2, 1)],
                                   fillstyles = [AmscoFillStyle.continuous, 
                                                 AmscoFillStyle.same_each_row, 
                                                 AmscoFillStyle.reverse_each_row],
                                   fitness=Pbigrams, 
                                   chunksize=500):
     """Breaks an AMSCO transposition cipher using a dictionary and
                                   fillstyles = [AmscoFillStyle.continuous, 
                                                 AmscoFillStyle.same_each_row, 
                                                 AmscoFillStyle.reverse_each_row],
                                   fitness=Pbigrams, 
                                   chunksize=500):
     """Breaks an AMSCO transposition cipher using a dictionary and
-    n-gram frequency analysis
+    n-gram frequency analysis.
+
+    If `translist` is not specified, use 
+    [`szyfrow.support.langauge_models.transpositions`](support/language_models.html#szyfrow.support.language_models.transpositions).
 
 
-    >>> amsco_break(amsco_transposition_encipher(sanitise( \
+    >>> amsco_break(amsco_encipher(sanitise( \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
@@ -162,7 +203,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)],
                    (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \
         patterns=[(1, 2)]) # doctest: +ELLIPSIS
     (((2, 0, 5, 3, 1, 4, 6), (1, 2), <AmscoFillStyle.continuous: 1>), -709.4646722...)
                    (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \
         patterns=[(1, 2)]) # doctest: +ELLIPSIS
     (((2, 0, 5, 3, 1, 4, 6), (1, 2), <AmscoFillStyle.continuous: 1>), -709.4646722...)
-    >>> amsco_break(amsco_transposition_encipher(sanitise( \
+    >>> amsco_break(amsco_encipher(sanitise( \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
@@ -176,6 +217,9 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)],
         patterns=[(1, 2), (2, 1)], fitness=Ptrigrams) # doctest: +ELLIPSIS
     (((2, 0, 5, 3, 1, 4, 6), (2, 1), <AmscoFillStyle.continuous: 1>), -997.0129085...)
     """
         patterns=[(1, 2), (2, 1)], fitness=Ptrigrams) # doctest: +ELLIPSIS
     (((2, 0, 5, 3, 1, 4, 6), (2, 1), <AmscoFillStyle.continuous: 1>), -997.0129085...)
     """
+    if translist is None:
+        translist = transpositions
+    
     with multiprocessing.Pool() as pool:
         helper_args = [(message, trans, pattern, fillstyle, fitness)
                        for trans in translist
     with multiprocessing.Pool() as pool:
         helper_args = [(message, trans, pattern, fillstyle, fitness)
                        for trans in translist
@@ -188,7 +232,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)],
 
 def amsco_break_worker(message, transposition,
         pattern, fillstyle, fitness):
 
 def amsco_break_worker(message, transposition,
         pattern, fillstyle, fitness):
-    plaintext = amsco_transposition_decipher(message, transposition,
+    plaintext = amsco_decipher(message, transposition,
         fillpattern=pattern, fillstyle=fillstyle)
     fit = fitness(sanitise(plaintext))
     return (transposition, pattern, fillstyle), fit
         fillpattern=pattern, fillstyle=fillstyle)
     fit = fitness(sanitise(plaintext))
     return (transposition, pattern, fillstyle), fit