По време на Втората Световна Война един от основните методи на нацистите да водят тайна комуникация е Енигма машината.
Процесът на работа на машината е "сравнително прост". Въвеждане на буква чрез клавиатурата генерира електрически сигнал, който преминава през няколко етапа на криптиране, и води до светлинна индикация на друга буква върху циферблата. В общи линии машината конвертира една буква в друга.
Конвертирането се реализира чрез минаване през следните компоненти:
Просто, нали?
Ние не искаме да не спите, затова ще опростим дизайна. Ще имаме само един ротор и няма да "отразяваме", т.е. само минаваме през Plugboard и Rotor и извеждаме резултата.
plugboard
, която приема два позиционни аргумента:str
с текста, който трябва да се конвертира от плъгборда. Конвертирането се прави буква по буква през целия символен низ.list
от set
-ове с по два елемента от тип str
. Всеки сет съдържа две букви, които са свързани, т.е. вход на буква, която е част от някой set
, ще се конвертира в съседката на тази буква от set
-а (set
-ът винаги съдържа две букви - едната се конвертира в другата и обратното). Функцията връща конвертиран str
, в който всяка буква е променила стойността си спрямо дефинициите в set
-а.
rotor
, която приема два позиционни аргумента:str
с текста, който трябва да се конвертира от ротора. Конвертирането се прави буква по буква през целия символен низ.dict
с ключове от тип str
и стойности от тип str
. Всеки ключ и стойност съдържат точно една буква. Речникът съдържа елементи с ключ за всяка от 26-те букви в латиницата. Всеки ключ сочи към стойност, отново една от 26-те букви в латиницата. За да бъде коректна задачата, речникът ще бъде конструиран по такъв начин, че всички 26 букви ще присъстват точно по веднъж, както в списъка от ключове, така и в списъка от стойности. Конвертирането се осъществява като всяка буква от символния низ, който трябва да се конвертира, промени стойността си спрямо речника. Всяка буква се търси в списъка от ключове на речника и променя стойността си на съответната стойност от речника, към която сочи този ключ.Функцията връща конвертиран str
, в който всяка буква е променила стойността си спрямо дефинициите в dict
-а.
enigma_encrypt
, която приема два именувани аргумента:plugboard_position
от тип list
, който съдържа елементи от тип set
с по два елемента от тип str
. Това е състоянието на плъгборда на машината. Виж функцията plugboard
.rotor_position
от тип dict
с ключове от тип str
и стойности от тип str
. Това е състоянието на ротора на машината. Виж функцията rotor
.Функцията връща декоратор, който може да бъде приложен на произволна функция, която очаква един позиционен аргумент от тип str
. Декораторът трябва да криптира стринга, преди да го подаде на функцията, която е декорирал. За криптирането се използват вече дефинираните функции plugboard
и rotor
със съответните стойности за състоянието на компонентите от машината, които enigma_encrypt
е получил на входа. Криптирането се извършва чрез минаване първо през плъгборда, след което през ротора.
enigma_decrypt
, която приема два именувани аргумента:plugboard_position
от тип list
, който съдържа елементи от тип set
с по два елемента от тип str
. Това е състоянието на плъгборда на машината. Виж функцията plugboard
.rotor_position
от тип dict
с ключове от тип str
и стойности от тип str
. Това е състоянието на ротора на машината. Виж функцията rotor
.Функцията връща декоратор, който може да бъде приложен на произволна функция, която очаква един позиционен аргумент от тип str
. Декораторът трябва да ДЕкриптира стринга, преди да го подаде на функцията, която е декорирал. За декриптирането се използват вече дефинираните функции plugboard
и rotor
със съответните стойности за състоянието на компонентите от машината, които enigma_dencrypt
е получил на входа. Декриптирането се извършва чрез минаване първо през ротора, след което през плъгборда.
Както винаги, няма да тестваме с невалиден вход. Ще се погрижим всичко, което влиза, да е валидно спрямо правилата по-горе, включително речниците за ротора да съдържат всяка буква от латиницата по точно един път и в ключовете, и в стойностите.
Приемете, че ще тестваме само с малки букви от латиницата и интервали.
Посоката на преминаване през плъгборда не е от значение, независимо дали криптирате, или ДЕкриптирате.
Посоката за преминаване през ротора, обаче, е от значение. При криптиране и декриптиране се очаква различно поведение. Не опитвайте да имплементирате това поведение във функцията rotor
. Тя има ясна и строга дефиниция.
Не опитвайте да заобиколите проблема като подавате различни стойностите за rotor_position
на enigma_encrypt
и enigma_decrypt
. Очаква се те винаги да работят с един и същ сет данни, който репрезентира състоянието на машината ви, както е показано в примера по-долу.
Спазете тези условия и се убедете, че при тестване на кода ви, ако резултатът от криптиране на текст се декриптира, получавате оригиналния текст, т.е. машината работи!
plugboard_position = [{'a', 'c'}, {'t', 'z'}]
rotor_position = {'v': 'd', 'd': 'v', 'y': 'u', 'n': 'n', 'i': 'w', 'z': 'p',
's': 'e', 'x': 's', 'h': 'f', 'b': 'x', 'u': 'c', 'p': 'q',
'r': 'g', 'q': 'j', 'e': 't', 'l': 'y', 'o': 'z', 'g': 'o',
'k': 'b', 't': 'h', 'j': 'm', 'a': 'a', 'w': 'i', 'f': 'l',
'm': 'r', 'c': 'k'}
rotor('enigma', rotor_position) # tnwora
plugboard('enigma', plugboard_position) # enigmc
encryptor = enigma_encrypt(plugboard_position=plugboard_position,
rotor_position=rotor_position)
decryptor = enigma_decrypt(plugboard_position=plugboard_position,
rotor_position=rotor_position)
encrypt_print = encryptor(print)
decrypt_print = decryptor(print)
encrypt_print('enigma') # tnwork
decrypt_print('tnwork') # enigma
1import types
2import unittest
3
4from solution import *
5
6
7PLUGBOARD_POSITION = [{'a', 'c'}, {'t', 'z'}]
8ROTOR_POSITION = {'v': 'd', 'd': 'v', 'y': 'u', 'n': 'n', 'i': 'w', 'z': 'p',
9 's': 'e', 'x': 's', 'h': 'f', 'b': 'x', 'u': 'c', 'p': 'q',
10 'r': 'g', 'q': 'j', 'e': 't', 'l': 'y', 'o': 'z', 'g': 'o',
11 'k': 'b', 't': 'h', 'j': 'm', 'a': 'a', 'w': 'i', 'f': 'l',
12 'm': 'r', 'c': 'k'}
13
14
15class TestPlugboard(unittest.TestCase):
16 """Test the plugboard function."""
17
18 def test_sanity(self):
19 """Sanity test for the plugboard function."""
20 self.assertIsInstance(plugboard('enigma', PLUGBOARD_POSITION), str)
21
22
23class TestRotor(unittest.TestCase):
24 """Test the rotor function."""
25
26 def test_sanity(self):
27 """Sanity test for the rotor function."""
28 self.assertIsInstance(rotor('enigma', ROTOR_POSITION), str)
29
30
31class TestEncryptor(unittest.TestCase):
32 """Test the encryptor function."""
33
34 def test_sanity(self):
35 """Sanity test for the encryptor function."""
36 self.assertIsInstance(enigma_encrypt(PLUGBOARD_POSITION, ROTOR_POSITION), types.FunctionType)
37
38
39class TestDecryptor(unittest.TestCase):
40 """Test the decryptor function."""
41
42 def test_sanity(self):
43 """Sanity test for the decryptor function."""
44 self.assertIsInstance(enigma_decrypt(PLUGBOARD_POSITION, ROTOR_POSITION), types.FunctionType)
45
46
47if __name__ == '__main__':
48 unittest.main()
1import types
2import unittest
3from unittest.mock import Mock
4
5from solution import *
6
7
8"""
9Code for generating random rotor position
10import random
11
12all_letters = []
13for x in range(97, 97 + 26):
14 all_letters.append(chr(x))
15
16keys = all_letters[:]
17vals = all_letters[:]
18random.shuffle(vals)
19
20d = {}
21for k, v in zip(keys, vals):
22 d[k] = v
23
24print(d)
25"""
26
27
28TEST_FUN = lambda x: x
29
30
31class TestPlugboard(unittest.TestCase):
32 """Test the plugboard function."""
33
34 def test_empty(self):
35 """Test the plugboard function with empty input."""
36 self.assertEqual(plugboard('', []), '')
37
38 def test_no_mapping(self):
39 """Test the plugboard function with no mapping input."""
40 self.assertEqual(plugboard('enigma machine is working', []), 'enigma machine is working')
41
42 def test_normal_case(self):
43 """Test the plugboard function with normally expected input."""
44 plugboard_position = [{'a', 'z'}, {'e', 'y'}, {'t', 'i'}]
45 self.assertEqual(plugboard('this is a test input', plugboard_position), 'ihts ts z iysi tnpui')
46
47
48class TestRotor(unittest.TestCase):
49 """Test the rotor function."""
50
51 ROTOR_POSITION = {'a': 'c', 'b': 'a', 'c': 'i',
52 'd': 'm', 'e': 'd', 'f': 'g',
53 'g': 'h', 'h': 'b', 'i': 'j',
54 'j': 'x', 'k': 'w', 'l': 'r',
55 'm': 'f', 'n': 'y', 'o': 'z',
56 'p': 'l', 'q': 'n', 'r': 'u',
57 's': 'o', 't': 'k', 'u': 'q',
58 'v': 's', 'w': 'e', 'x': 'p',
59 'y': 't', 'z': 'v'}
60
61 def test_empty(self):
62 """Test the rotor function with empty input."""
63 self.assertEqual(rotor('', self.ROTOR_POSITION), '')
64
65 def test_normal_case(self):
66 """Test the rotor function with normally expected input."""
67 self.assertEqual(rotor('this is a test input', self.ROTOR_POSITION), 'kbjo jo c kdok jylqk')
68
69
70class TestEncryptor(unittest.TestCase):
71 """Test the encryptor function."""
72
73 PLUGBOARD_POSITION = [{'a', 'z'}, {'u', 'f'}, {'m', 'n'}, {'o', 'w'}]
74
75 ROTOR_POSITION = {'a': 'o', 'b': 'n', 'c': 's',
76 'd': 'e', 'e': 'g', 'f': 'a',
77 'g': 'w', 'h': 'j', 'i': 'v',
78 'j': 'h', 'k': 'k', 'l': 'i',
79 'm': 'y', 'n': 'u', 'o': 'f',
80 'p': 'l', 'q': 'c', 'r': 'r',
81 's': 'p', 't': 'm', 'u': 'z',
82 'v': 'x', 'w': 'q', 'x': 'd',
83 'y': 'b', 'z': 't'}
84
85 def test_full_letter_set(self):
86 """Test the encryptor function with all letters in the rotor."""
87 encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
88 encrypted = encryptor(TEST_FUN)
89 self.assertEqual(encrypted('the quick brown fox jumps over the lazy dog'),
90 'mjg cavsk nrqfy zqd haulp qxgr mjg itob eqw')
91
92
93class TestDecryptor(unittest.TestCase):
94 """Test the decryptor function."""
95
96 PLUGBOARD_POSITION = [{'f', 'u'}, {'g', 'q'}, {'b', 'v'}, {'r', 's'}]
97
98 ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
99 'd': 's', 'e': 'x', 'f': 'u',
100 'g': 'f', 'h': 'l', 'i': 'v',
101 'j': 'g', 'k': 'r', 'l': 'p',
102 'm': 'o', 'n': 'n', 'o': 'a',
103 'p': 'y', 'q': 't', 'r': 'q',
104 's': 'b', 't': 'm', 'u': 'j',
105 'v': 'c', 'w': 'k', 'x': 'd',
106 'y': 'w', 'z': 'h'}
107
108 def test_full_letter_set(self):
109 """Test the decryptor function with all letters in the rotor."""
110 decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
111 decrypted = decryptor(TEST_FUN)
112 self.assertEqual(decrypted('mlx fuver cbakn jad guoyq aixb mlx pzhw sat'),
113 'the quick brown fox jumps over the lazy dog')
114
115
116class TestCombination(unittest.TestCase):
117 """Test decrypting an encrypted text."""
118
119 PLUGBOARD_POSITION = [{'a', 'b'}, {'c', 'd'}, {'e', 'f'}, {'g', 'h'},
120 {'i', 'j'}, {'k', 'l'}, {'m', 'n'}, {'o', 'p'},
121 {'q', 'r'}, {'s', 't'}, {'u', 'v'}, {'w', 'x'},
122 {'y', 'z'}]
123
124 ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
125 'd': 's', 'e': 'x', 'f': 'u',
126 'g': 'f', 'h': 'l', 'i': 'v',
127 'j': 'g', 'k': 'r', 'l': 'p',
128 'm': 'o', 'n': 'n', 'o': 'a',
129 'p': 'y', 'q': 't', 'r': 'q',
130 's': 'b', 't': 'm', 'u': 'j',
131 'v': 'c', 'w': 'k', 'x': 'd',
132 'y': 'w', 'z': 'h'}
133
134 def test_full_letter_set(self):
135 """Test decrypting an encrypted text against itself."""
136 encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
137 decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
138 combined = decryptor(encryptor(TEST_FUN))
139 self.assertEqual(combined('i love python'), 'i love python')
140
141
142class TestDecorators(unittest.TestCase):
143 """Test decorators."""
144
145 PLUGBOARD_POSITION = [{'a', 'b'}, {'c', 'd'}, {'e', 'f'}, {'g', 'h'},
146 {'i', 'j'}, {'k', 'l'}, {'m', 'n'}, {'o', 'p'},
147 {'q', 'r'}, {'s', 't'}, {'u', 'v'}, {'w', 'x'},
148 {'y', 'z'}]
149
150 ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
151 'd': 's', 'e': 'x', 'f': 'u',
152 'g': 'f', 'h': 'l', 'i': 'v',
153 'j': 'g', 'k': 'r', 'l': 'p',
154 'm': 'o', 'n': 'n', 'o': 'a',
155 'p': 'y', 'q': 't', 'r': 'q',
156 's': 'b', 't': 'm', 'u': 'j',
157 'v': 'c', 'w': 'k', 'x': 'd',
158 'y': 'w', 'z': 'h'}
159
160 def test_correct_decorator_order(self):
161 """Test whether the decorator is applying the functions in correct order."""
162 encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
163 decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
164 mock = Mock()
165 encryptor(mock)('test')
166 mock.assert_called_once_with('bumb')
167 mock = Mock()
168 decryptor(mock)('bumb')
169 mock.assert_called_once_with('test')
170
171
172if __name__ == '__main__':
173 unittest.main()
![]()
Александър Ангелов
26.10.2023 16:32позволявате ли функцията ми rotor да приема трети аргумент - флаг isDecrypt, за да е по-лесно декриптирането? ПП оправих се, намерих по-прост начин, но не ми дава да си изтрия коментара (за добро предполагам). |