Домашни > Енигма


Енигма
Краен срок: 31.10.2023 18:00
Точки: 10

### Въведение По време на Втората Световна Война един от основните методи на нацистите да водят тайна комуникация е Енигма машината. ![Enigma Design](/media/resources/enigma.png) Процесът на работа на машината е "сравнително прост". Въвеждане на буква чрез клавиатурата генерира електрически сигнал, който преминава през няколко етапа на криптиране, и води до светлинна индикация на друга буква върху циферблата. В общи линии машината конвертира една буква в друга. Конвертирането се реализира чрез минаване през следните компоненти: - Буквата за вход се чете от **клавиатурата**; - преминава през **"Plugboard"**, в който даден чифт букви *може* (но не задължително) да се свърже заедно, което води до промяна на едната буква от чифта в другата; - например "x" се свързва с "z", което значи, че ако входът е "x", изходът е "z" и обратното; - ако буквата, която влиза като вход, не е част от двойка, т.е. не е свързана с друга чрез кабел, тя не се променя; - продължава през **"Rotors"**, в който буквата, получена като резултат от предишната стъпка, преминава през поредица от три ротора, всеки от които: - променя буквата на входа на друга буква; - завърта се на една от 26 възможни позиции, което променя корелацията на вход/изход между отделните ротори. - буквата се "отразява" от края на ротора и минава по същия път, но в обратен ред; - буквата се извежда чрез светлинна индикация. Просто, нали? ### Задачата Ние не искаме да не спите, затова ще опростим дизайна. Ще имаме само един ротор и няма да "отразяваме", т.е. само минаваме през 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()
Дискусия
Георги Кунчев
31.10.2023 08:47

@Веселина, при еднакви подадени стойности на `enigma_encrypt` и `enigma_decrypt`, и условието, че не можете да добавяте повече параметри от дефинираните на която и да било от функциите, в общи линии не ти остават много варианти, така че най-вероятно наистина трябва да "правим някакви магии в декоратора". Не е задължително да е така, но е валидно решение. Хинтът е важен, за да ви подтикнем да "правите магии", а не да очаквате, че ние ще тестваме с различна стойност за енкриптора и декриптора. Съдейки по прочетеното, правилно си разбрала условието.
Веселина Велкова
31.10.2023 01:06

Здравейте, не разбирам тази част от хинта - "Не опитвайте да заобиколите проблема като подавате различни стойностите за rotor_position на enigma_encrypt и enigma_decrypt". Ако encryptor = enigma _ encrypt(plugboard _ position=plugboard _ position, rotor _ position=rotor _ position) / decryptor = enigma _ decrypt(plugboard _ position=plugboard _ position, rotor _ position=rotor _ position) тук сме подали едни и същи rotor _ position, е ок да си правим някакви магии в декоратора нали? Извинявам се, ако съм дала някаква подсказка на останалите, просто не съм сигурна как мога да си задам правилно въпроса :(
Георги Кунчев
30.10.2023 14:05

@Даниел, конвертирането със сет не е обръвзано с ред. Която и да е буква от сета се конвертира в съседната си. Дефиниции като "първата" и "втората" буква от сета не са коректни.
Даниел Йорданов
30.10.2023 13:43

Понеже знаем, че set не гарантира подредбата на данните, коректно ли е да го използваме за точно конвертиране от първата във втората буква от него?
Георги Кунчев
29.10.2023 10:05

Съответните празни колекции ще свършат работа, но сори да нямаш стойности по подразбиране, те пак могат да се подадат като именувани.
Добромир Пеев
29.10.2023 09:56

След като по условие аргументите в enigma_decrypt и enigme_encrypt са именувани в такъв случай какви стойности да им дадем по подразбиране ?
Георги Кунчев
26.10.2023 18:51

@Николай, личи си кой не е ползвал `pytest`. Печелиш звезда.
Николай Николаев
26.10.2023 18:44

И аз благодаря за обратната връзка! Тестовете действително са за pytest. Има автоматично откриване на тестовете, благодарение на имената на функциите, които започват с test. Вярно е, че изглеждат само като функции, но това е приятното на pytest - няма сложен синтаксис и при писане на pytest [името на файла с тестовете] в терминал пак се възползваме от благинките.
Георги Кунчев
26.10.2023 18:21

@Александър, радвам се, че си намерил решение. Като цяло отговорът би бил не. Семантиката на функциите трябва да съвпада с това, което сме дефинирали в условието. @Николай, благодаря за тестовете от името на колегите! Съветвам да се опиташ да използваш `unittest` или `pytest` за следващия път. Това са просто функции и не черпят от всички благинки, които библиотеките ти предлагат.
Николай Николаев
26.10.2023 16:38

Това са мои unit тестове, използващи pytest. Дано са ви полезни :smile: ``` """ This is a set of unit tests for homework 2 that uses PyTest. If you want to use them to test your homework, install PyTest (pip install pytest in your terminal). The tests are manually written and yield correct results with my solution, but if you find any inconsistencies, feel free to comment below. """ from homework2 import plugboard, rotor, 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'} @enigma_encrypt(plugboard_position, rotor_position) def encrypt_decorated(text): """Helper to test the encryption decorator.""" return text @enigma_decrypt(plugboard_position, rotor_position) def decrypt_decorated(text): """Helper to test the decryption decorator.""" return text def test_plugboard(): """Tests the plugboard function.""" assert(plugboard("enigma", plugboard_position)) == "enigmc" assert(plugboard("aarcetzot", plugboard_position)) == "ccraeztoz" assert(plugboard("", plugboard_position)) == "" assert(plugboard("t", plugboard_position)) == "z" assert(plugboard("c", plugboard_position)) == "a" assert(plugboard("tctatctzac", plugboard_position)) == "zazczaztca" assert(plugboard("a t", plugboard_position)) == "c z" assert(plugboard(" a t ", plugboard_position)) == " c z " def test_rotor(): """Tests the rotor function.""" assert(rotor("enigmc", rotor_position)) == "tnwork" assert(rotor("e", rotor_position)) == "t" assert(rotor("e c", rotor_position)) == "t k" assert(rotor("", rotor_position)) == "" assert(rotor("eeee", rotor_position)) == "tttt" assert(rotor("abcdefghijklmopqrstuvwxyz", rotor_position)) == "axkvtlofwmbyrzqjgehcdisup" def test_enigma_encrypt(): """Tests the enigma_encrypt decorator.""" assert(encrypt_decorated("enigma")) == "tnwork" assert(encrypt_decorated("abcdefghijklmopqrstuvwxyz")) == "kxavtlofwmbyrzqjgepcdisuh" assert(encrypt_decorated("a")) == "k" assert(encrypt_decorated("")) == "" assert(encrypt_decorated("cat")) == "akp" def test_enigma_decrypt(): """Tests the enigma_decrypt decorator.""" assert(decrypt_decorated("tnwork")) == "enigma" assert(decrypt_decorated("kxavtlofwmbyrzqjgepcdisuh")) == "abcdefghijklmopqrstuvwxyz" assert(decrypt_decorated("k")) == "a" assert(decrypt_decorated("")) == "" assert(decrypt_decorated("akp")) == "cat" ```