+ adjoint = linalg.det(matrix)*linalg.inv(matrix)
+ inverse_determinant = modular_division_table[int(round(linalg.det(matrix))) % 26][1]
+ inverse_matrix = (inverse_determinant * adjoint) % 26
+ return hill_encipher(inverse_matrix, message, fillvalue)
+
+
+# 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'
+AmscoSlice = collections.namedtuple('AmscoSlice', ['index', 'start', 'end'])
+
+class AmscoFillStyle(Enum):
+ continuous = 1
+ same_each_row = 2
+ reverse_each_row = 3
+
+def amsco_transposition_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_transposition_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 = cycle(fillpattern)
+ indices = 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 = cycle(fillpattern)
+ if fillstyle == AmscoFillStyle.reverse_each_row:
+ fill_iterator = 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_transposition_encipher(message, keyword,
+ fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
+ """AMSCO transposition encipher.
+
+ >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(1, 2))
+ 'hoteelhler'
+ >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(2, 1))
+ 'hetelhelor'
+ >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(1, 2))
+ 'hotelerelh'
+ >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(2, 1))
+ 'hetelorlhe'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'encode')
+ 'etecstthhomoerereenisxip'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
+ 'hetcsoeisterereipexthomn'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hecsoisttererteipexhomen'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
+ 'heecisoosttrrtepeixhemen'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
+ 'hxtomephescieretoeisnter'
+ >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hxomeiphscerettoisenteer'
+ """
+ grid = amsco_transposition_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_transposition_decipher(message, keyword,
+ fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
+ """AMSCO transposition decipher
+
+ >>> amsco_transposition_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
+ 'hellothere'
+ >>> amsco_transposition_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
+ 'hellothere'
+ >>> amsco_transposition_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
+ 'hellothere'
+ >>> amsco_transposition_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
+ 'hellothere'
+ >>> amsco_transposition_decipher('etecstthhomoerereenisxip', 'encode')
+ 'hereissometexttoencipher'
+ >>> amsco_transposition_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
+ 'hereissometexttoencipher'
+ >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hereissometexttoencipher'
+ >>> amsco_transposition_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
+ 'hereissometexttoencipher'
+ >>> amsco_transposition_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
+ 'hereissometexttoencipher'
+ >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hereissometexttoencipher'
+ """
+
+ grid = amsco_transposition_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 bifid_grid(keyword, wrap_alphabet, letter_mapping):
+ """Create the grids for a Bifid cipher
+ """
+ cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
+ if letter_mapping is None:
+ letter_mapping = {'j': 'i'}
+ translation = ''.maketrans(letter_mapping)
+ cipher_alphabet = cat(collections.OrderedDict.fromkeys(cipher_alphabet.translate(translation)))
+ f_grid = {k: ((i // 5) + 1, (i % 5) + 1)
+ for i, k in enumerate(cipher_alphabet)}
+ r_grid = {((i // 5) + 1, (i % 5) + 1): k
+ for i, k in enumerate(cipher_alphabet)}
+ return translation, f_grid, r_grid
+
+def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
+ letter_mapping=None, period=None, fillvalue=None):
+ """Bifid cipher
+
+ >>> bifid_encipher("indiajelly", 'iguana')
+ 'ibidonhprm'
+ >>> bifid_encipher("indiacurry", 'iguana', period=4)
+ 'ibnhgaqltm'
+ >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x')
+ 'ibnhgaqltzml'
+ """
+ translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
+
+ t_message = message.translate(translation)
+ pairs0 = [f_grid[l] for l in sanitise(t_message)]
+ if period:
+ chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
+ if len(chunked_pairs[-1]) < period and fillvalue:
+ chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+ else:
+ chunked_pairs = [pairs0]
+
+ pairs1 = []
+ for c in chunked_pairs:
+ items = sum(list(list(i) for i in zip(*c)), [])
+ p = [(items[i], items[i+1]) for i in range(0, len(items), 2)]
+ pairs1 += p
+
+ return cat(r_grid[p] for p in pairs1)
+
+
+def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
+ letter_mapping=None, period=None, fillvalue=None):
+ """Decipher with bifid cipher
+
+ >>> bifid_decipher('ibidonhprm', 'iguana')
+ 'indiaielly'
+ >>> bifid_decipher("ibnhgaqltm", 'iguana', period=4)
+ 'indiacurry'
+ >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4)
+ 'indiacurryxx'
+ """
+ translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
+
+ t_message = message.translate(translation)
+ pairs0 = [f_grid[l] for l in sanitise(t_message)]
+ if period:
+ chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
+ if len(chunked_pairs[-1]) < period and fillvalue:
+ chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+ else:
+ chunked_pairs = [pairs0]
+
+ pairs1 = []
+ for c in chunked_pairs:
+ items = [j for i in c for j in i]
+ gap = len(c)
+ p = [(items[i], items[i+gap]) for i in range(gap)]
+ pairs1 += p