Design

Saturday, November 13, 2010

Caesar Cipher in Python Using ASCII

ASCII is how American computers store numbers, letters, certain commands, and symbols as numbers. A binary byte is eight digits long, consisting of only 1 and 0. When ASCII was developed, there were 2^8, or 256 possible characters for 8-bit (1 byte) personal computers. Unicode is used more commonly and extensively, but ASCII is sufficient or this exercise. Consult an ASCII Table or play with python's default functions to determine the encoding of a character such as 'a', 'A' or '&'.

In python, we can find the ascii value using ord of a string
>>> ord('a')
97
>>> ord('A')
65
>>> ord('b')
98
>>> ord('1')
49
>>> ord('2')
50
>>> 3-0
3
>>> ord('3')-ord('0')
3

Notice how the lower case and upper case are unique. Also, the ASCII numeric value and the character increase by the same amount. The same trend is true for the continuation of the alphabet, if lower case or upper case characteristic is preserved. The string length must be 1 when using ord, because each index has its own ASCII value.For a multiple digit number or a word, you can use a for loop to go through the string.

The reverse of ord is chr, for character.
>>> chr(97)
'a'
>>> i=97
>>> while i<104:
... print chr(i)
... i+=1
...
a
b
c
d
e
f
g
>>> alpha=[]
>>> i=97
>>> while i<104:
... alpha.append(chr(i))
... i+=1
...
>>> print alpha
['a', 'b', 'c', 'd', 'e', 'f', 'g']


Caesar's shift was how Caesar communicated with his generals, in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, if an 'a' is encoded by a 'c' with a shift of 2, then a 'b' would be encoded by a 'd.' Reversely, an 'a' in the encoded text would be a 'y' in the plaintext. The following program was inspired by thepythonchallenge.com. My husband and I had a lot of fun testing and debugging by sending each other messages over email. The code only changes letters so that spaces, smilies, brackets, and other punctuations and characters will be preserved. string.maketrans would be very useful and less code for a one time application, but the following code is highly reusable.


#string will later allow us to test for ascii letters
import string

def encode(input,shift):
#create an empty string
foo = ''
#a for loop to shift the letters by the shift input
for x in input:
#changes only ascii letters
if x in string.ascii_letters:
#upper case end of alphabet, to loop back to A
if ord(x) > ord("Z") - shift and ord(x) <= ord("Z"):
new_ord = ord(x) + shift - 26
#lower case end of alphabet
elif ord(x) > ord("z") - shift and ord(x) <= ord("z"):
new_ord = ord(x) + shift - 26
else:
new_ord = ord(x) + shift
#other characters will remain unchanged
else:
new_ord = ord(x)

new_chr = chr(new_ord)
foo += new_chr

print "Your encoded message is ", foo

def decode(input,shift):
#create an empty string
foo = ''
#a for loop to shift the leters by the shift input
for x in input:
#changes only ascii letters
if x in string.ascii_letters:
#upper case start of alphabet, to loop back to Z
if ord(x) - shift < ord("A") and ord(x) >= ord("A"):
new_ord = ord(x) - shift + 26
#lower case end of alphabet
elif ord(x) - shift < ord("a") and ord(x) >= ord("a"):
new_ord = ord(x) - shift + 26
else:
new_ord = ord(x) - shift
#other characters will remain unchanged
else:
new_ord = ord(x)
new_chr = chr(new_ord)
foo += new_chr
print "Your decoded message is ", foo


def get_shift():
try:
shift = int(raw_input("How many characters do you want to\
shift?\n"))
except:
print 'Shift must be a number'
shift = get_shift()
if not (shift > 0 and shift <= 25):
print 'Shift must be between 1 and 25'
shift = get_shift()
return shift

def main():
try:
action=raw_input("Welcome to Caeser's Shifter! Would you \
like to encode or decode a message today?\n")
except:
print 'Please type encode or decode'
main()
shift = get_shift()

print "Ok, let's %s your message with a shift of %d." % (action, shift)
input=raw_input("What would you like to be translated?\n")

if action=='encode':
encode(input,shift)
elif action=='decode':
decode(input,shift)

if __name__ == "__main__":
main()


There are probably a few areas where redundancy can be reduced, but I am still learning as a developer. This was my first time trying exception handling, which I learned from my husband.

8 comments:

  1. This looks like a great example to get familiar with how ASCII works!

    I think if you tried, you could still use string.maketrans, though. Just use it inside your encode() and decode() functions to make them shorter!

    Here are a couple hints you might find useful:
    -string has a couple useful strings, namely ascii_lowercase and ascii_uppercase.
    -strings are a type of sequence, so they support notation known as "slicing" which can help you "rotate" the alphabet.
    -decoding is really just encoding in reverse.

    Good luck and have fun!

    ReplyDelete
  2. Cool that you're programming (I don't know you, but learning to program is cool for everyone)

    There is an operator module (%), which could make your if < 'a', if >'z' a bit simpler. Might be good to play in the interactive interpreter with what it does (30%26) etc. Could you use that in your program to make some things simpler?

    If you have modified for that, the next question is, why can't I pass a negative number as shift? It might be easier for me to say -3 to shift back, and have your program figure out that it is the same as +23.

    If you have that, do you actually need a separate encode and decode?

    Enjoy the programming!

    ReplyDelete
  3. Nice job. However, are you sure you need a separate function for encode and decode, with all the logic duplicated? Isn't a decode the same as encode with just a negative shift?

    ReplyDelete
  4. Great job! Learning to program is very fun. On thing that could reduce your code significantly is combining the ord function and the chr function.

    >>> ord("A")
    65
    >>> chr(65)
    A

    Think about how you could use that. It will make your code much shorter and more flexible.

    ReplyDelete
  5. It is discouraged to catch all exceptions with the blanket use of except. If you can, try to catch exceptions explicitly with the following syntax.

    except ExceptionType, exceptionObject:
    Now you can pull apart the exceptionObject parts, such as the stack trace and message if desired. It prevents the situation where you expected one type of exception but actually caught another, masking a possible bug.

    ReplyDelete