HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Increase the speed of this Caesar Cipher

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thisthecaesarincreasespeedcipher

Problem

Here's my attempt at a Caesar Cipher encoder/decoder.

If given a key, it will encrypt the given string. However, if you do not specify a key, it checks each of the 26 possible keys and returns the one with the highest percentage of words that appear in this file of English words (with a couple additions including 'a' and 'I'). The program also returns the 'assurance' in its choice; for a specified key it is always 100%, for an unspecified key it is the percentage of words that are in English.

How can I improve the speed of the program? I have gone through 5 versions and improved the method that I use to decrypt each time, but now I would like to improve the speed. As I am a beginner, I am not very sure on how to do this.

The code is fairly readable, I think.

CaesarCipher_v5.py [47 lines]

```
# CaesarCipher_v5.py
#
# CSTAICH 2014

from string import maketrans, ascii_uppercase, ascii_lowercase
import re
from operator import itemgetter
from itertools import chain

input = ''
key = ''

# variable input and processing
while input == '': input = raw_input('Input ciphertext: ')
if key == '': key = raw_input('Input key; leave blank for auto-detection: ')
if key != '':
key = int(key)
if key >= 26: raise Exception("invalid key, must be [0-25]")
alphabet = list(chain(*zip(ascii_uppercase, ascii_lowercase)))
english_words = re.sub(r'\r', '', open('sowpods.txt', 'r').read().lower()).split('\n')

# =================================-------------------------------

def key_shift(input, key): # shifts an input by a key number of characters
return input.translate(maketrans(str(alphabet), str(alphabet[key 2:] + alphabet[:key 2])))

def english(sentence): #returns percentage of words in input that are english words
sentence = re.sub(r'[?,.!:;/]', '', sentence).split(' ') #strip punctuation and split into words
number_english_words = 0
number_words = len(sentence)
for word in sentence:
if word.lower() in english_words: number_english_words

Solution

Your code would be much clearer if split into multiple functions, with clear inputs and outputs, e.g.:

def encode(plaintext, key):
    ...

def decode(ciphertext, key=None, dictionary=None):
    ...

def get_int_input(prompt, max_=26):
    ...


This is much better than relying on scope for access to the objects your functions need.

You could then have a loop at the end to run it all, something like:

if __name__ == '__main__':
    english_words = create_dictionary()
    while True:
        choice = get_int_input("1. Encode\n2. Decode\n3. Exit\n", 3)
        if choice == 1:
            plaintext = raw_input("Enter the plain text: ")
            key = get_int_input("Enter the key: ", 26)
            print encode(plaintext, key)
        elif choice == 2:
            ciphertext = raw_input("Enter the cipher text: ")
            key = None
            if raw_input("Do you know the key? (y/n) ".lower()) == "y":
                key = get_int_input("Enter the key: ", 26)
            plaintext, match = decode(ciphertext, key, dictionary)
            print "{0} :: {1:.2f}".format(plaintext, match)
        else:
            break


This improves the reusability of your code by allowing you to import the functions elsewhere without actually running the

In terms of speed, one obvious improvement would be to use a set, which provides very fast membership testing using hashing, for your dictionary. Also, you seem to be building it in an awkward way, try:

def create_dictionary(filename='sowpods.txt'):
    with open(filename) as f:
        return set(line.strip().lower() for line in f)


As you can now loop multiple times, you could also think about storing the translations in a dictionary {key: translation}, so you only build them once.

Code Snippets

def encode(plaintext, key):
    ...

def decode(ciphertext, key=None, dictionary=None):
    ...

def get_int_input(prompt, max_=26):
    ...
if __name__ == '__main__':
    english_words = create_dictionary()
    while True:
        choice = get_int_input("1. Encode\n2. Decode\n3. Exit\n", 3)
        if choice == 1:
            plaintext = raw_input("Enter the plain text: ")
            key = get_int_input("Enter the key: ", 26)
            print encode(plaintext, key)
        elif choice == 2:
            ciphertext = raw_input("Enter the cipher text: ")
            key = None
            if raw_input("Do you know the key? (y/n) ".lower()) == "y":
                key = get_int_input("Enter the key: ", 26)
            plaintext, match = decode(ciphertext, key, dictionary)
            print "{0} :: {1:.2f}".format(plaintext, match)
        else:
            break
def create_dictionary(filename='sowpods.txt'):
    with open(filename) as f:
        return set(line.strip().lower() for line in f)

Context

StackExchange Code Review Q#60961, answer score: 3

Revisions (0)

No revisions yet.