#!/usr/bin/env python """ Chapter 4, exercise 15 from Zelle's "Python Programming" (Note: the solution I have here is a bit more than I'd expect you folks to come up with after only a few weeks of programming, but it does show you what can be done with the tools you already have.) Essentially the exercise is to write a program to encode and decode strings of text by shifting each character to a different one. We want to do this in a way that"wraps around", so if for example 'a' is shifted to the next character 'b', then 'z' is shifted (say) to 'a'. The simplest way to do this is using the ascii numeric representations of characters. For example, char = 'w' # a character to encode, e.g. 'w' shift = 10 # how many letters to shift it ascii = ord(char) # in ascii, e.g. 119 for 'w' zero_to_25 = ascii - ord('a') # from 0 to 25, e.g. 22 for 'w' shifted = (zero_to_25 + shift) % 26 # add 10 mod 26, e.g. 6 for 'w' new_char = chr(shifted + ord('a')) # the final encoded char, e.g. 'g' However, this won't handle uppercase, spaces, newlines, or numbers. One way to deal with these other characters is to use cycle through all the printable ascii characters (see for example http://en.wikipedia.org/wiki/ASCII), which are ord(' ')=32 through ord('~')=126 ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' thus shifting these 95 printable chars into each other. However, newlines (chr(10)) will still be handled incorrectly, So this approach requires you to (if you're doing file input/output) read in the file by lines, remove the newlines from each line, encode/decode, and then put the newlines back in. For example plain = open('input.txt', 'r') coded = open('output.txt', 'w') lines = plain.read_lines() # list of each line for line in lines: line = line[0:-1] # remove the last (newline) character line = encode(line) line = line + "\n" # replace the newline at the end coded.write(line) But here I'm going to show an entirely different idea, which uses lists and and the string.find() function instead of ascii. The trick is to have two lists, one with the 'plain' version of each character, and one with the 'coded' version of the character, and to use the python string functions to convert each character from those in one list to those in the other. The advantage to this approach is that the code can be in any order we want, and some characters (like space and newline) can be be given special treatment. One disadvantage is that find() and the construction of these lists is relatively slow compared to the ascii arithmetic; however, for our purposes that doesn't matter. Since we want to do this for an arbitrary shift, the program will have to construct these lists for the particular n we have in mind ... but that's not too hard. For example, for shift n=2 the lists might be plain = "abcdefghijklmnopqrstuvwxyz0123456789\n.," coded = "cdefghijklmnopqrstuvwxyzab3346789012\n.," with the lowercase and numbers cycling separately, and newline, period, and comma remaining unchanged. (Uppercase should be in there, too, and prob'ly more punctuation. But you get the idea.) The string function that's going to be particularly useful is find(). >>> from string import find >>> plainchars = 'abcde' >>> find(plainchars, 'd') 3 which gets the index in 'abcde' of where the 'd' character is. Then if we have some other list, say 'cdeab' (which is shifted by 2 characters), we can use the index to do the encode, e.g. >>> code = 'cdeab' >>> code[3] 'a' which says that 'd' shifted by two characters is 'a' The rest is pretty straightforward. Jim Mahoney, Sep 25 """ def string_of_chars(first, last): """Return a string of consecutive characters. >>> string_of_chars('a', 'd') 'abcd' """ answer = '' for ascii in range(ord(first), ord(last)+1): answer = answer + chr(ascii) return answer def rotate_string(string, n): """Return a string of characters rotated by n. >>> rotate_string('abcde', 2) 'cdeab' """ answer = '' for i in range(0, len(string)): j = (i + n) % len(string) answer = answer + string[j] return answer def encode(plaintext, n): """Convert a plaintext string to an encoded string. Lowercase, Uppercase, and numbers are shifted by n characters. Newlines, periods, commas, and other punctuation are unchanged. >>> encode("What!? No, I think it *can* be done.", 13) Jung!? Ab, V guvax vg *pna* or qbar. """ from string import find lower = string_of_chars('a', 'z') upper = string_of_chars('A', 'Z') nums = string_of_chars('0', '9') punc = " \t\n`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?" plainchars = lower + upper + nums + punc codedchars = rotate_string(lower, n) + rotate_string(upper, n) + \ rotate_string(nums, n) + punc answer = '' for char in plaintext: index = find(plainchars, char) coded = codedchars[index] answer = answer + coded return answer def main(): print " -- Zelle chapter 4 exercise 9 - Ceasar cipher --" n = input(" What character offset do you want? (e.g. 13) ") print " Please type a string to encode." plain = raw_input(" plaintext: ") coded = encode(plain, n) print " The encoded version of that is " print " ciphertext: " + coded main()