# Caesar ciphers ![centre-aligned Caesar wheel](caesarwheel1.gif) Letter-by-letter enciphering --- # Enciphering and deciphering ## Arithmetic on letters Convert .plaintext[letter] → .plaintext[number] → .ciphertext[number] → .ciphertext[letter] Functions you will need ```python ord() chr() % ``` * What are good test cases? --- # The [string module](http://docs.python.org/3.3/library/string.html) is your friend ```python import string string.ascii_letters string.ascii_lowercase string.ascii_uppercase string.digits string.punctuation ``` --- # DRY and YAGNI Is your code DRY? --- # Doctest * Why document? * Why test? ```python def caesar_encipher_letter(letter, shift): """Encipher a letter, given a shift amount >>> caesar_encipher_letter('a', 1) 'b' """ if letter in string.ascii_letters: . . . ``` --- # The magic doctest incantation ```python if __name__ == "__main__": import doctest doctest.testmod() ``` --- # Accents ```python >>> caesar_encipher_letter('é', 1) ``` What does it produce? What should it produce? ## Unicode, combining codepoints, and normal forms Text encodings will bite you when you least expect it. * urlencoding is the other pain point. --- # Five minutes on StackOverflow later... ```python def unaccent(text): """Remove all accents from letters. It does this by converting the unicode string to decomposed compatibility form, dropping all the combining accents, then re-encoding the bytes. >>> unaccent('hello') 'hello' >>> unaccent('HELLO') 'HELLO' >>> unaccent('héllo') 'hello' >>> unaccent('héllö') 'hello' >>> unaccent('HÉLLÖ') 'HELLO' """ return unicodedata.normalize('NFKD', text).\ encode('ascii', 'ignore').\ decode('utf-8') ``` --- # Doing all the letters ## Test-first developement 1. Write the tests. * They will fail. There is no code. 2. Write code until the tests pass. 3. Refactor. --- # Doing all the letters ## Abysmal ```python ciphertext = '' for i in range(len(plaintext)): ciphertext += caesar_encipher_letter(plaintext[i], key) ``` --- # Doing all the letters ## Bad ```python ciphertext = '' for p in plaintext: ciphertext += caesar_encipher_letter(p, key) ``` ...but easily generalisable --- # Doing all the letters ## Good (but unPythonic) ```python ciphertext = map(lambda p: caesar_encipher_letter(p, key), plaintext) ``` --- # Doing all the letters ## Best ```python ciphertext = [caesar_encipher_letter(p, key) for p in plaintext] ``` --- # Not all iterables are equal ```python ''.join() ```