Module szyfrow.amsco

Enciphering and deciphering using 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".

Expand source code
"""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 *

__pdoc__ = {}

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):
    """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

def amsco_positions(message, keyword, 
      fillpattern=(1, 2),
      fillstyle=AmscoFillStyle.continuous,
      fillcolumnwise=False,
      emptycolumnwise=True):
    """Creates the grid for the AMSCO transposition cipher. Each element in the
    grid shows the index of that slice and the start and end positions of the
    plaintext that go to make it up.

    >>> 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),
     AmscoSlice(index=0, start=0, end=1),
     AmscoSlice(index=1, start=1, end=3),
     AmscoSlice(index=4, start=6, end=7)],
    [AmscoSlice(index=8, start=12, end=13),
     AmscoSlice(index=7, start=10, end=12),
     AmscoSlice(index=5, start=7, end=9),
     AmscoSlice(index=6, start=9, end=10),
     AmscoSlice(index=9, start=13, end=15)],
    [AmscoSlice(index=13, start=19, end=21),
     AmscoSlice(index=12, start=18, end=19),
     AmscoSlice(index=10, start=15, end=16),
     AmscoSlice(index=11, start=16, end=18),
     AmscoSlice(index=14, start=21, end=22)],
    [AmscoSlice(index=18, start=27, end=28),
     AmscoSlice(index=17, start=25, end=27),
     AmscoSlice(index=15, start=22, end=24),
     AmscoSlice(index=16, start=24, end=25),
     AmscoSlice(index=19, start=28, end=30)]]
    """
    transpositions = transpositions_of(keyword)
    fill_iterator = itertools.cycle(fillpattern)
    indices = itertools.count()
    message_length = len(message)

    current_position = 0
    grid = []
    current_fillpattern = fillpattern
    while current_position < message_length:
        row = []
        if fillstyle == AmscoFillStyle.same_each_row:
            fill_iterator = itertools.cycle(fillpattern)
        if fillstyle == AmscoFillStyle.reverse_each_row:
            fill_iterator = itertools.cycle(current_fillpattern)
        for _ in range(len(transpositions)):
            index = next(indices)
            gap = next(fill_iterator)
            row += [AmscoSlice(index, current_position, current_position + gap)]
            current_position += gap
        grid += [row]
        if fillstyle == AmscoFillStyle.reverse_each_row:
            current_fillpattern = list(reversed(current_fillpattern))
    return [transpose(r, transpositions) for r in grid]

def amsco_encipher(message, keyword, 
    fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
    """AMSCO transposition encipher.

    >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2))
    'hoteelhler'
    >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1))
    'hetelhelor'
    >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2))
    'hotelerelh'
    >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1))
    'hetelorlhe'
    >>> amsco_encipher('hereissometexttoencipher', 'encode')
    'etecstthhomoerereenisxip'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
    'hetcsoeisterereipexthomn'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
    'hecsoisttererteipexhomen'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
    'heecisoosttrrtepeixhemen'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
    'hxtomephescieretoeisnter'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
    'hxomeiphscerettoisenteer'
    """
    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)


def amsco_decipher(message, keyword, 
    fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
    """AMSCO transposition decipher

    >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
    'hellothere'
    >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
    'hellothere'
    >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
    'hellothere'
    >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
    'hellothere'
    >>> amsco_decipher('etecstthhomoerereenisxip', 'encode')
    'hereissometexttoencipher'
    >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
    'hereissometexttoencipher'
    >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
    'hereissometexttoencipher'
    >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
    'hereissometexttoencipher'
    >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
    'hereissometexttoencipher'
    >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
    'hereissometexttoencipher'
    """

    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)
    current_pos = 0
    for slice in transposed_sections:
        plaintext_list[slice.index] = message[current_pos:current_pos-slice.start+slice.end][:len(message[slice.start:slice.end])]
        current_pos += len(message[slice.start:slice.end])
    return cat(plaintext_list)


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
    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_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 \
             first entering a neighbourhood, this truth is so well fixed in \
             the minds of the surrounding families, that he is considered the \
             rightful property of some one or other of their daughters."), \
        'encipher'), \
        translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
                   (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
                   (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_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 \
             first entering a neighbourhood, this truth is so well fixed in \
             the minds of the surrounding families, that he is considered the \
             rightful property of some one or other of their daughters."), \
        'encipher', fillpattern=(2, 1)), \
        translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
                   (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
                   (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \
        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
                       for pattern in patterns
                       for fillstyle in fillstyles]
        # Gotcha: the helper function here needs to be defined at the top level
        #   (limitation of Pool.starmap)
        breaks = pool.starmap(amsco_break_worker, helper_args, chunksize) 
        return max(breaks, key=lambda k: k[1])

def amsco_break_worker(message, transposition,
        pattern, fillstyle, fitness):
    plaintext = amsco_decipher(message, transposition,
        fillpattern=pattern, fillstyle=fillstyle)
    fit = fitness(sanitise(plaintext))
    return (transposition, pattern, fillstyle), fit

if __name__ == "__main__":
    import doctest

Functions

def amsco_break(message, translist=None, patterns=[(1, 2), (2, 1)], fillstyles=[<AmscoFillStyle.continuous: 1>, <AmscoFillStyle.same_each_row: 2>, <AmscoFillStyle.reverse_each_row: 3>], fitness=<function Pbigrams>, chunksize=500)

Breaks an AMSCO transposition cipher using a dictionary and n-gram frequency analysis.

If translist is not specified, use szyfrow.support.langauge_models.transpositions.

>>> 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              first entering a neighbourhood, this truth is so well fixed in              the minds of the surrounding families, that he is considered the              rightful property of some one or other of their daughters."),         'encipher'),         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'],                    (5, 0, 6, 1, 3, 4, 2): ['fourteen'],                    (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_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              first entering a neighbourhood, this truth is so well fixed in              the minds of the surrounding families, that he is considered the              rightful property of some one or other of their daughters."),         'encipher', fillpattern=(2, 1)),         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'],                    (5, 0, 6, 1, 3, 4, 2): ['fourteen'],                    (6, 1, 0, 4, 5, 3, 2): ['keyword']},         patterns=[(1, 2), (2, 1)], fitness=Ptrigrams) # doctest: +ELLIPSIS
(((2, 0, 5, 3, 1, 4, 6), (2, 1), <AmscoFillStyle.continuous: 1>), -997.0129085...)
Expand source code
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
    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_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 \
             first entering a neighbourhood, this truth is so well fixed in \
             the minds of the surrounding families, that he is considered the \
             rightful property of some one or other of their daughters."), \
        'encipher'), \
        translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
                   (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
                   (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_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 \
             first entering a neighbourhood, this truth is so well fixed in \
             the minds of the surrounding families, that he is considered the \
             rightful property of some one or other of their daughters."), \
        'encipher', fillpattern=(2, 1)), \
        translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
                   (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
                   (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \
        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
                       for pattern in patterns
                       for fillstyle in fillstyles]
        # Gotcha: the helper function here needs to be defined at the top level
        #   (limitation of Pool.starmap)
        breaks = pool.starmap(amsco_break_worker, helper_args, chunksize) 
        return max(breaks, key=lambda k: k[1])
def amsco_break_worker(message, transposition, pattern, fillstyle, fitness)
Expand source code
def amsco_break_worker(message, transposition,
        pattern, fillstyle, fitness):
    plaintext = amsco_decipher(message, transposition,
        fillpattern=pattern, fillstyle=fillstyle)
    fit = fitness(sanitise(plaintext))
    return (transposition, pattern, fillstyle), fit
def amsco_decipher(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.reverse_each_row)

AMSCO transposition decipher

>>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
'hellothere'
>>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
'hellothere'
>>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
'hellothere'
>>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
'hellothere'
>>> amsco_decipher('etecstthhomoerereenisxip', 'encode')
'hereissometexttoencipher'
>>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
'hereissometexttoencipher'
>>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
'hereissometexttoencipher'
>>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
'hereissometexttoencipher'
>>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
'hereissometexttoencipher'
>>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
'hereissometexttoencipher'
Expand source code
def amsco_decipher(message, keyword, 
    fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
    """AMSCO transposition decipher

    >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
    'hellothere'
    >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
    'hellothere'
    >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
    'hellothere'
    >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
    'hellothere'
    >>> amsco_decipher('etecstthhomoerereenisxip', 'encode')
    'hereissometexttoencipher'
    >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
    'hereissometexttoencipher'
    >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
    'hereissometexttoencipher'
    >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
    'hereissometexttoencipher'
    >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
    'hereissometexttoencipher'
    >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
    'hereissometexttoencipher'
    """

    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)
    current_pos = 0
    for slice in transposed_sections:
        plaintext_list[slice.index] = message[current_pos:current_pos-slice.start+slice.end][:len(message[slice.start:slice.end])]
        current_pos += len(message[slice.start:slice.end])
    return cat(plaintext_list)
def amsco_encipher(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.reverse_each_row)

AMSCO transposition encipher.

>>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2))
'hoteelhler'
>>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1))
'hetelhelor'
>>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2))
'hotelerelh'
>>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1))
'hetelorlhe'
>>> amsco_encipher('hereissometexttoencipher', 'encode')
'etecstthhomoerereenisxip'
>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
'hetcsoeisterereipexthomn'
>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
'hecsoisttererteipexhomen'
>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
'heecisoosttrrtepeixhemen'
>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
'hxtomephescieretoeisnter'
>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
'hxomeiphscerettoisenteer'
Expand source code
def amsco_encipher(message, keyword, 
    fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
    """AMSCO transposition encipher.

    >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2))
    'hoteelhler'
    >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1))
    'hetelhelor'
    >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2))
    'hotelerelh'
    >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1))
    'hetelorlhe'
    >>> amsco_encipher('hereissometexttoencipher', 'encode')
    'etecstthhomoerereenisxip'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
    'hetcsoeisterereipexthomn'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
    'hecsoisttererteipexhomen'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
    'heecisoosttrrtepeixhemen'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
    'hxtomephescieretoeisnter'
    >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
    'hxomeiphscerettoisenteer'
    """
    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)
def amsco_positions(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous, fillcolumnwise=False, emptycolumnwise=True)

Creates the grid for the AMSCO transposition cipher. Each element in the grid shows the index of that slice and the start and end positions of the plaintext that go to make it up.

>>> 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),
 AmscoSlice(index=0, start=0, end=1),
 AmscoSlice(index=1, start=1, end=3),
 AmscoSlice(index=4, start=6, end=7)],
[AmscoSlice(index=8, start=12, end=13),
 AmscoSlice(index=7, start=10, end=12),
 AmscoSlice(index=5, start=7, end=9),
 AmscoSlice(index=6, start=9, end=10),
 AmscoSlice(index=9, start=13, end=15)],
[AmscoSlice(index=13, start=19, end=21),
 AmscoSlice(index=12, start=18, end=19),
 AmscoSlice(index=10, start=15, end=16),
 AmscoSlice(index=11, start=16, end=18),
 AmscoSlice(index=14, start=21, end=22)],
[AmscoSlice(index=18, start=27, end=28),
 AmscoSlice(index=17, start=25, end=27),
 AmscoSlice(index=15, start=22, end=24),
 AmscoSlice(index=16, start=24, end=25),
 AmscoSlice(index=19, start=28, end=30)]]
Expand source code
def amsco_positions(message, keyword, 
      fillpattern=(1, 2),
      fillstyle=AmscoFillStyle.continuous,
      fillcolumnwise=False,
      emptycolumnwise=True):
    """Creates the grid for the AMSCO transposition cipher. Each element in the
    grid shows the index of that slice and the start and end positions of the
    plaintext that go to make it up.

    >>> 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),
     AmscoSlice(index=0, start=0, end=1),
     AmscoSlice(index=1, start=1, end=3),
     AmscoSlice(index=4, start=6, end=7)],
    [AmscoSlice(index=8, start=12, end=13),
     AmscoSlice(index=7, start=10, end=12),
     AmscoSlice(index=5, start=7, end=9),
     AmscoSlice(index=6, start=9, end=10),
     AmscoSlice(index=9, start=13, end=15)],
    [AmscoSlice(index=13, start=19, end=21),
     AmscoSlice(index=12, start=18, end=19),
     AmscoSlice(index=10, start=15, end=16),
     AmscoSlice(index=11, start=16, end=18),
     AmscoSlice(index=14, start=21, end=22)],
    [AmscoSlice(index=18, start=27, end=28),
     AmscoSlice(index=17, start=25, end=27),
     AmscoSlice(index=15, start=22, end=24),
     AmscoSlice(index=16, start=24, end=25),
     AmscoSlice(index=19, start=28, end=30)]]
    """
    transpositions = transpositions_of(keyword)
    fill_iterator = itertools.cycle(fillpattern)
    indices = itertools.count()
    message_length = len(message)

    current_position = 0
    grid = []
    current_fillpattern = fillpattern
    while current_position < message_length:
        row = []
        if fillstyle == AmscoFillStyle.same_each_row:
            fill_iterator = itertools.cycle(fillpattern)
        if fillstyle == AmscoFillStyle.reverse_each_row:
            fill_iterator = itertools.cycle(current_fillpattern)
        for _ in range(len(transpositions)):
            index = next(indices)
            gap = next(fill_iterator)
            row += [AmscoSlice(index, current_position, current_position + gap)]
            current_position += gap
        grid += [row]
        if fillstyle == AmscoFillStyle.reverse_each_row:
            current_fillpattern = list(reversed(current_fillpattern))
    return [transpose(r, transpositions) for r in grid]
def cat(iterable, /)

Concatenate any number of strings.

The string whose method is called is inserted in between each given string. The result is returned as a new string.

Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'

def lcat(iterable, /)

Concatenate any number of strings.

The string whose method is called is inserted in between each given string. The result is returned as a new string.

Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'

def wcat(iterable, /)

Concatenate any number of strings.

The string whose method is called is inserted in between each given string. The result is returned as a new string.

Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'

Classes

class AmscoFillStyle (value, names=None, *, module=None, qualname=None, type=None, start=1)

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

Expand source code
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

Ancestors

  • enum.Enum

Class variables

var continuous
var reverse_each_row
var same_each_row
class AmscoSlice (index, start, end)

Where each piece of plainatext ends up in the AMSCO transpositon cipher.

Ancestors

  • builtins.tuple

Instance variables

var end

Where the slice ends in the plaintext

var index

Where the slice appears in the plaintext

var start

Where the slice starts in the plaintext