Updated for challenge 9
[cipher-tools.git] / cipher / cadenus.py
1 from itertools import chain
2 import multiprocessing
3 from support.utilities import *
4 from support.language_models import *
5 from cipher.column_transposition import transpositions_of
6
7 from logger import logger
8
9 def make_cadenus_keycolumn(doubled_letters = 'vw', start='a', reverse=False):
10 """Makes the key column for a Cadenus cipher (the column down between the
11 rows of letters)
12
13 >>> make_cadenus_keycolumn()['a']
14 0
15 >>> make_cadenus_keycolumn()['b']
16 1
17 >>> make_cadenus_keycolumn()['c']
18 2
19 >>> make_cadenus_keycolumn()['v']
20 21
21 >>> make_cadenus_keycolumn()['w']
22 21
23 >>> make_cadenus_keycolumn()['z']
24 24
25 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['a']
26 1
27 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['b']
28 0
29 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['c']
30 24
31 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['i']
32 18
33 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['j']
34 18
35 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['v']
36 6
37 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['z']
38 2
39 """
40 index_to_remove = string.ascii_lowercase.find(doubled_letters[0])
41 short_alphabet = string.ascii_lowercase[:index_to_remove] + string.ascii_lowercase[index_to_remove+1:]
42 if reverse:
43 short_alphabet = cat(reversed(short_alphabet))
44 start_pos = short_alphabet.find(start)
45 rotated_alphabet = short_alphabet[start_pos:] + short_alphabet[:start_pos]
46 keycolumn = {l: i for i, l in enumerate(rotated_alphabet)}
47 keycolumn[doubled_letters[0]] = keycolumn[doubled_letters[1]]
48 return keycolumn
49
50 def cadenus_encipher(message, keyword, keycolumn, fillvalue='a'):
51 """Encipher with the Cadenus cipher
52
53 >>> cadenus_encipher(sanitise('Whoever has made a voyage up the Hudson ' \
54 'must remember the Kaatskill mountains. ' \
55 'They are a dismembered branch of the great'), \
56 'wink', \
57 make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True))
58 'antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaasuvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned'
59 >>> cadenus_encipher(sanitise('a severe limitation on the usefulness of ' \
60 'the cadenus is that every message must be ' \
61 'a multiple of twenty-five letters long'), \
62 'easy', \
63 make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True))
64 'systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtofarenuseieeieltarlmentieetogevesitfaisltngeeuvowul'
65 """
66 rows = chunks(message, len(message) // 25, fillvalue=fillvalue)
67 columns = zip(*rows)
68 rotated_columns = [col[start:] + col[:start] for start, col in zip([keycolumn[l] for l in keyword], columns)]
69 rotated_rows = zip(*rotated_columns)
70 transpositions = transpositions_of(keyword)
71 transposed = [transpose(r, transpositions) for r in rotated_rows]
72 return cat(chain(*transposed))
73
74 def cadenus_decipher(message, keyword, keycolumn, fillvalue='a'):
75 """
76 >>> cadenus_decipher('antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaa' \
77 'suvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned', \
78 'wink', \
79 make_cadenus_keycolumn(reverse=True))
80 'whoeverhasmadeavoyageupthehudsonmustrememberthekaatskillmountainstheyareadismemberedbranchofthegreat'
81 >>> cadenus_decipher('systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtof' \
82 'arenuseieeieltarlmentieetogevesitfaisltngeeuvowul', \
83 'easy', \
84 make_cadenus_keycolumn(reverse=True))
85 'aseverelimitationontheusefulnessofthecadenusisthateverymessagemustbeamultipleoftwentyfiveletterslong'
86 """
87 rows = chunks(message, len(message) // 25, fillvalue=fillvalue)
88 transpositions = transpositions_of(keyword)
89 untransposed_rows = [untranspose(r, transpositions) for r in rows]
90 columns = zip(*untransposed_rows)
91 rotated_columns = [col[-start:] + col[:-start] for start, col in zip([keycolumn[l] for l in keyword], columns)]
92 rotated_rows = zip(*rotated_columns)
93 # return rotated_columns
94 return cat(chain(*rotated_rows))
95
96
97 def cadenus_break(message, words=keywords,
98 doubled_letters='vw', fitness=Pbigrams):
99 c = make_cadenus_keycolumn(reverse=True)
100 valid_words = [w for w in words
101 if max(transpositions_of(w)) <= len(c)]
102 with multiprocessing.Pool() as pool:
103 results = pool.starmap(cadenus_break_worker,
104 [(message, w,
105 make_cadenus_keycolumn(doubled_letters=doubled_letters,
106 start=s, reverse=r),
107 fitness)
108 for w in words
109 for s in string.ascii_lowercase
110 for r in [True, False]
111 if max(transpositions_of(w)) <= len(
112 make_cadenus_keycolumn(
113 doubled_letters=doubled_letters, start=s, reverse=r))
114 ])
115 # return list(results)
116 return max(results, key=lambda k: k[1])
117
118 def cadenus_break_worker(message, keyword, keycolumn, fitness):
119 message_chunks = chunks(message, 175)
120 plaintext = ''.join(cadenus_decipher(c, keyword, keycolumn) for c in message_chunks)
121 fit = fitness(plaintext)
122 return (keyword, keycolumn), fit
123
124 if __name__ == "__main__":
125 import doctest