Solution Exercise 38

Do the same Caesar chipher as in the previous example but the time use ROT-3, that is rotate the letters three steps. Create two functions, one for coding messages and one for decoding them.

Hint:
You might want to use two dictionaries to solve this problem.

Here is the first version of the program. Look below for other solutions.

#Create two dictionaries. This one is used to encipher the message
encipherkey = {'a' : 'd' ,'b' : 'e' ,'c' : 'f' ,'d' : 'g' ,'e' : 'h' ,'f' : 'i' ,'g' : 'j' ,'h' : 'k' ,
               'i' : 'l' ,'j' : 'm' ,'k' : 'n' ,'l' : 'o' ,'m' : 'p' ,'n' : 'q' ,'o' : 'r' ,'p' : 's' ,
               'q' : 't' ,'r' : 'u' ,'s' : 'v' ,'t' : 'w' ,'u' : 'x' ,'v' : 'y' ,'w' : 'z' ,'x' : 'a' ,
               'y' : 'b' ,'z' : 'c' ,'A' : 'D' ,'B' : 'E' ,'C' : 'F' ,'D' : 'G' ,'E' : 'H' ,'F' : 'I' ,
               'G' : 'J' ,'H' : 'K' ,'I' : 'L' ,'J' : 'M' ,'K' : 'N' ,'L' : 'O' ,'M' : 'P' ,'N' : 'Q' ,
               'O' : 'R' ,'P' : 'S' ,'Q' : 'T' ,'R' : 'U' ,'S' : 'V' ,'T' : 'W' ,'U' : 'X' ,'V' : 'Y' ,
               'W' : 'Z' ,'X' : 'A' ,'Y' : 'B' ,'Z' : 'C'  }

#And this one is used to decipher the message
decipherkey = {'a' : 'x' ,'b' : 'y' ,'c' : 'z' ,'d' : 'a' ,'e' : 'b' ,'f' : 'c' ,'g' : 'd' ,'h' : 'e' ,
               'i' : 'f' ,'j' : 'g' ,'k' : 'h' ,'l' : 'i' ,'m' : 'j' ,'n' : 'k' ,'o' : 'l' ,'p' : 'm' ,
               'q' : 'n' ,'r' : 'o' ,'s' : 'p' ,'t' : 'q' ,'u' : 'r' ,'v' : 's' ,'w' : 't' ,'x' : 'u' ,
               'y' : 'v' ,'z' : 'w' ,'A' : 'X' ,'B' : 'Y' ,'C' : 'Z' ,'D' : 'A' ,'E' : 'B' ,'F' : 'C' ,
               'G' : 'D' ,'H' : 'E' ,'I' : 'F' ,'J' : 'G' ,'K' : 'H' ,'L' : 'I' ,'M' : 'J' ,'N' : 'K' ,
               'O' : 'L' ,'P' : 'M' ,'Q' : 'N' ,'R' : 'O' ,'S' : 'P' ,'T' : 'Q' ,'U' : 'R' ,'V' : 'S' ,
               'W' : 'T' ,'X' : 'U' ,'Y' : 'V' ,'Z' : 'W'  }

#This function uses the encipher dictionary
def encode(text):
    result = ""
    for c in text:
        #Only look up letters
        if c.isalpha():
            result += encipherkey[c]
        else: #Any other character will be stored as is
            result += c
    return result

#This one is identical to the encode function except it uses the decipher dictionary
def decode(text):
    result = ""
    for c in text:
        #Only look up letters
        if c.isalpha():
            result += decipherkey[c]
        else: #Any other character will be stored as is
            result += c
    return result

def main():
    encoded = encode("This is my secret!")
    decoded = decode(encoded)
    print(encoded)
    print(decoded)

if __name__ == '__main__':
    main()

Below is a better version of the program. Here we generate the dicitionaies in two functions and we pass the generated dictionaries to the encipher function so we can use it for both encryption and decryption.

#Instead of creating fixed dictionaries I decided to solve this
#by creating to functions that generates the dictionaries
#By doing this I can pass in a value for how many steps I want 
#the key dict to be rotated
def create_encoding_dict(rot):
    #First we create the lower case letters
    encoding_dict = {}
    #a-z has the ascii values of 97-122
    for i in range(97,123):
        #We need to check to see if it is time to wrap the alphabet around
        if i + rot < 122:
            encoding_dict[chr(i)] = chr(i+rot)
        else: #Yes wrap
            encoding_dict[chr(i)] = chr(i -26+rot)
    #Now take care of the upper case letters A-Z (65-90)
    for i in range(65,91):
        #We need to check to see if it is time to wrap the alphabet around
        if i + rot < 90:
            encoding_dict[chr(i)] = chr(i+rot)
        else: #YEs wrap
            encoding_dict[chr(i)] = chr(i -26+rot)
    return encoding_dict


def create_decoding_dict(rot):
        #First we create the lower case letters
    decoding_dict = {}
    #a-z has the ascii values of 97-122
    for i in range(97,123):
        #We need to check to see if it is time to wrap the alphabet around
        if i - rot >= 97:
            decoding_dict[chr(i)] = chr(i-rot)
        else: #YEs wrap
            decoding_dict[chr(i)] = chr(i +26-rot)
    #Now take care of the upper case letters A-Z (65-90)
    for i in range(65,91):
        #We need to check to see if it is time to wrap the alphabet around
        if i - rot >= 65:
            decoding_dict[chr(i)] = chr(i-rot)
        else: #Yes wrap
            decoding_dict[chr(i)] = chr(i +26-rot)
    return decoding_dict

#If we pass in the key dict to use we can use the same function for encoding and decoding
def encipher(text, key):
    result = ""
    for c in text:
        #Only look up letters
        if c.isalpha():
            result += key[c]
        else: #Any other character will be stored as is
            result += c
    return result


def main():
    enchipherKey = create_encoding_dict(3)
    decipherKey = create_decoding_dict(3) 
    encoded = encipher("This is a secret message!",enchipherKey)
    decoded = encipher(encoded,decipherKey)
    print(encoded)
    print(decoded)

This final version does not use any functions but instead it uses list comprehension and itertools chain function to generate the tables. Those are used with the maketrans and translate functions from the string class

from itertools import chain
def main():
    cleartext = "This is a secret message"
    ciphertext = cleartext.translate(str.maketrans(
                                    "".join([chr(x) for x in chain(range(65,91),range(97,123))]),
                                    "".join(chain([chr(65+(x+3)%26) for x in range(26)],
                                                  [chr(97+(x+3)%26) for x in range(26)]))))
    cleartext = ciphertext.translate(str.maketrans(
                                    "".join(chain([chr(65+(x+3)%26) for x in range(26)],
                                                  [chr(97+(x+3)%26) for x in range(26)])),
                                    "".join([chr(x) for x in chain(range(65,91),range(97,123))])))

    print(ciphertext)
    print(cleartext)