# Keyword ciphers a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z --|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|-- k | e | y | w | o | r | d | a | b | c | f | g | h | i | j | l | m | n | p | q | s | t | u | v | x | z * Taking a more Pythonic approach --- # The cipher * Still character-by-character substitution, still monosubstitution. Ciphertext alphabet: start with a keyword, write out the rest of the alphabet, removing duplicates. ## Three variants Write out the rest of the alphabet... 1. ...starting from 'a' (keywordabcf...) 2. ...starting from the last letter of the keyword (keywordfgh...) 3. ...starting from the largest letter of the keyword (keywordzabc...) --- # A more Pythonic way _string_`.translate()` and _string_`.maketrans()` * Make the 'ciphertext' alphabet, relate to the 'plaintext' alphabet (`string.ascii_lowercase`) * Use those to make the translation table * Enciphering is simply applying `plaintext.translate(enciphering_table)` * Deciphering just uses a different table --- # Making the cipher alphabet from a keyword Three challenges: 1. How to say which type of cipher alphabet to use 2. Where to start the rest of the alphabet 3. Removing duplicate letters Solutions: 1. Keyword arguments for procedures 2. sort and slices 3. Use something like an ordered set Both enciphering and deciphering need the same keyword-based alphabet, so pull this out into another procedure. --- # Keyword arguments Used to: 1. give a default value for a parameter 2. allow parameters to be named (not just positional) Give our `keyword_encipher` and `keyword_decipher` procedures a keyword parameter of `wrap_alphabet`. Pass this parameter to the `keyword_alphabet` procedure. ## wrap_alphabet has no inherent meaning Use Python 3.4's `Enum` ```python from enum import Enum class Keyword_wrap_alphabet(Enum): from_a = 1 from_last = 2 from_largest = 3 ``` (Use integers in earlier Pythons) --- # Deduplicating a sequence We need * Something set-like * Something ordered No ordered set in Python, but do have an ordered dict. * Keys of a dict are a set. * Keys in an ordered dict retain their order (subsequent instances are ignored) `deduplicated_list = list(collections.OrderedDict.fromkeys(list))` --- # Sorts and slices ## Recap Write out the rest of the alphabet... 1. ...starting from 'a' (keywordabcf...) 2. ...starting from the last letter of the keyword (keywordfgh...) 3. ...starting from the largest letter of the keyword (keywordzabc...) * Santitise the keyword before we use it --- # Making the keyword alphabet ## Cases 1. As we're deduplicating anyway, just add the entire alphabet to the end of the keyword, then deduplicate. `deduplicate(keyword + string.ascii_lowercase)` 2. and 3. How to find the appropriate letter of the keyword. `deduplicate(keyword + string_ascii_lowercase[from:] + string.ascii_lowercase)` Question: why not take a slice of the second alphabet copy? Question: what do we use as the last letter of 'character'? 'r' or 'e'? `sorted()` will put a string in lexicographical order. `.find()` will find an item in a sequence --- # Keyword enciphering Now we've got the keyword-based cipher alphabet, simply use `.translate()` to do the enciphering/deciphering. Use `''.maketrans()` to make the translation table. Sorted! Does it pass the tests?