a51955acad335d9409aca193fe39b7e9eb15e487
1 from support
.utilities
import *
2 from support
.language_models
import *
4 from logger
import logger
7 class PocketEnigma(object):
8 """A pocket enigma machine
9 The wheel is internally represented as a 26-element list self.wheel_map,
10 where wheel_map[i] == j shows that the position i places on from the arrow
11 maps to the position j places on.
13 def __init__(self
, wheel
=1, position
='a'):
14 """initialise the pocket enigma, including which wheel to use and the
15 starting position of the wheel.
17 The wheel is either 1 or 2 (the predefined wheels) or a list of letter
20 The position is the letter pointed to by the arrow on the wheel.
23 [25, 4, 23, 10, 1, 7, 9, 5, 12, 6, 3, 17, 8, 14, 13, 21, 19, 11, 20, 16, 18, 15, 24, 2, 22, 0]
27 self
.wheel1
= [('a', 'z'), ('b', 'e'), ('c', 'x'), ('d', 'k'),
28 ('f', 'h'), ('g', 'j'), ('i', 'm'), ('l', 'r'), ('n', 'o'),
29 ('p', 'v'), ('q', 't'), ('s', 'u'), ('w', 'y')]
30 self
.wheel2
= [('a', 'c'), ('b', 'd'), ('e', 'w'), ('f', 'i'),
31 ('g', 'p'), ('h', 'm'), ('j', 'k'), ('l', 'n'), ('o', 'q'),
32 ('r', 'z'), ('s', 'u'), ('t', 'v'), ('x', 'y')]
34 self
.make_wheel_map(self
.wheel1
)
36 self
.make_wheel_map(self
.wheel2
)
38 self
.validate_wheel_spec(wheel
)
39 self
.make_wheel_map(wheel
)
40 if position
in string
.ascii_lowercase
:
41 self
.position
= pos(position
)
43 self
.position
= position
45 def make_wheel_map(self
, wheel_spec
):
46 """Expands a wheel specification from a list of letter-letter pairs
47 into a full wheel_map.
49 >>> pe.make_wheel_map(pe.wheel2)
50 [2, 3, 0, 1, 22, 8, 15, 12, 5, 10, 9, 13, 7, 11, 16, 6, 14, 25, 20, 21, 18, 19, 4, 24, 23, 17]
52 self
.validate_wheel_spec(wheel_spec
)
53 self
.wheel_map
= [0] * 26
55 self
.wheel_map
[pos(p
[0])] = pos(p
[1])
56 self
.wheel_map
[pos(p
[1])] = pos(p
[0])
59 def validate_wheel_spec(self
, wheel_spec
):
60 """Validates that a wheel specificaiton will turn into a valid wheel
63 >>> pe.validate_wheel_spec([])
64 Traceback (most recent call last):
66 ValueError: Wheel specification has 0 pairs, requires 13
67 >>> pe.validate_wheel_spec([('a', 'b', 'c')]*13)
68 Traceback (most recent call last):
70 ValueError: Not all mappings in wheel specificationhave two elements
71 >>> pe.validate_wheel_spec([('a', 'b')]*13)
72 Traceback (most recent call last):
74 ValueError: Wheel specification does not contain 26 letters
76 if len(wheel_spec
) != 13:
77 raise ValueError("Wheel specification has {} pairs, requires 13".
78 format(len(wheel_spec
)))
81 raise ValueError("Not all mappings in wheel specification"
83 if len(set([p
[0] for p
in wheel_spec
] +
84 [p
[1] for p
in wheel_spec
])) != 26:
85 raise ValueError("Wheel specification does not contain 26 letters")
87 def encipher_letter(self
, letter
):
88 """Enciphers a single letter, by advancing the wheel before looking up
89 the letter on the wheel.
91 >>> pe.set_position('f')
93 >>> pe.encipher_letter('k')
97 return self
.lookup(letter
)
98 decipher_letter
= encipher_letter
100 def lookup(self
, letter
):
101 """Look up what a letter enciphers to, without turning the wheel.
103 >>> pe.set_position('f')
105 >>> cat([pe.lookup(l) for l in string.ascii_lowercase])
106 'udhbfejcpgmokrliwntsayqzvx'
110 if letter
in string
.ascii_lowercase
:
112 (self
.wheel_map
[(pos(letter
) - self
.position
) % 26] +
118 """Advances the wheel one position.
120 >>> pe.set_position('f')
125 self
.position
= (self
.position
+ 1) % 26
128 def encipher(self
, message
, starting_position
=None):
129 """Enciphers a whole message.
131 >>> pe.set_position('f')
133 >>> pe.encipher('helloworld')
135 >>> pe.set_position('f')
137 >>> pe.encipher('kjsglcjoqc')
139 >>> pe.encipher('helloworld', starting_position = 'x')
142 if starting_position
:
143 self
.set_position(starting_position
)
146 transformed
+= self
.encipher_letter(l
)
150 def set_position(self
, position
):
151 """Sets the position of the wheel, by specifying the letter the arrow
154 >>> pe.set_position('a')
156 >>> pe.set_position('m')
158 >>> pe.set_position('z')
161 self
.position
= pos(position
)
165 def pocket_enigma_break_by_crib(message
, wheel_spec
, crib
, crib_position
):
166 """Break a pocket enigma using a crib (some plaintext that's expected to
167 be in a certain position). Returns a list of possible starting wheel
168 positions that could produce the crib.
170 >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'h', 0)
172 >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'he', 0)
174 >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'll', 2)
176 >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 2)
178 >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 3)
180 >>> pocket_enigma_break_by_crib('aaaaa', 1, 'l', 3)
183 pe
= PocketEnigma(wheel
=wheel_spec
)
184 possible_positions
= []
185 for p
in string
.ascii_lowercase
:
187 plaintext
= pe
.decipher(message
)
188 if plaintext
[crib_position
:crib_position
+len(crib
)] == crib
:
189 possible_positions
+= [p
]
190 return possible_positions
192 if __name__
== "__main__":
194 doctest
.testmod(extraglobs
={'pe': PocketEnigma(1, 'a')})