Python Caesar Cipher Decoder

Question:

In my lesson I was tasked with creating a Caesar Cipher decoder that takes a string of input and finds the best possible string using a letter frequencies. If not sure how much sense that made but let post the question:

Write a program which does the following. First, it should read one line of input, which is the encoded message, and will consist of capital letters and spaces. Your program must try decoding the message with all 26 possible values of the shift S; out of these 26 possible original messages, print the one which has the highest goodness.
For your convenience, we will pre-define the variable letterGoodness for you, a list of length 26 which equals the values in the frequency table above

Letter Frequencies

I have this code so far:

x = input()
NUM_LETTERS = 26 #Can't import modules I'm using a web based grader/compiler
def SpyCoder(S, N):
    y = ""
    for i in S:
        x = ord(i)
        x += N
        if x > ord('Z'):
            x -= NUM_LETTERS
        elif x < ord('A'):
            x += NUM_LETTERS
        y += chr(x)
    return y  

def GoodnessFinder(S):
    y = 0
    for i in S:
        if x != 32:
            x = ord(i)
            x -= ord('A')
            y += letterGoodness[x]
    return y 

def GoodnessComparer(S):
    goodnesstocompare = GoodnessFinder(S)
    goodness = 0
    v = ''
    for i in range(0, 26):
        v = SpyCoder(S, i)
        goodness = GoodnessFinder(v)
        if goodness > goodnesstocompare:
            goodnesstocompare = goodness
    return v

y = x.split()
z = ''
for i in range(0, len(y)):
    if i == len(y) - 1:
        z += GoodnessComparer(y[i])
print(z)

EDIT: Made changes suggested by Cristian Ciupitu
Please ignore indentation errors, they probably arose when I copied my code over.

The program works like this:

  • Take the input and split it into a list
  • For every list value I feed it to a goodness finder.
  • It takes the goodness of the string and compares everything else against and when there’s a higher goodness it makes the higher one the goodness to compare.
  • It then shifts that string of text by i amount to see if the goodness is higher or lower

I’m not quite sure where the problem is, the first test: LQKP OG CV GKIJV DA VJG BQQ
Prints the correct message: JOIN ME AT AT BY THE ZOO

However the next test: UIJT JT B TBNQMF MJOF PG UFYU GPS EFDSZQUJOH
Gives the a junk string of: SGHR HR Z RZLOKD KHMD NE SDWS ENQ CDBQXOSHMF
When it’s supposed to be: THIS IS A SAMPLE LINE OF TEXT FOR DECRYPTING

I know I have to:
Try every shift value
Get the ‘goodness’ of the word
Return the string with the highest goodness.

I hope my explanation made sense as I am quite confused at the moment.

Asked By: Thegluestickman

||

Answers:

Here is my implementation which works fine.

You should print the goodness of each possible message and see why your program output it.

letterGoodness = dict(zip(string.ascii_uppercase,
                        [.0817,.0149,.0278,.0425,.1270,.0223,.0202,
                         .0609,.0697,.0015,.0077,.0402,.0241,.0675,
                         .0751,.0193,.0009,.0599,.0633,.0906,.0276,
                         .0098,.0236,.0015,.0197,.0007]))

trans_tables = [ str.maketrans(string.ascii_uppercase,
                 string.ascii_uppercase[i:]+string.ascii_uppercase[:i])
                 for i in range(26)]

def goodness(msg):
    return sum(letterGoodness.get(char, 0) for char in msg)

def all_shifts(msg):
    msg = msg.upper()
    for trans_table in trans_tables:
        txt = msg.translate(trans_table)
        yield goodness(txt), txt

print(max(all_shifts(input())))
Answered By: Kabie

My final solution that works, thanks to the wonderful Cristian Ciupitu.

x = input()
NUM_LETTERS = 26 #Can't import modules I'm using a web based grader/compiler
def SpyCoder(S, N):
   y = ""
   for i in S:
      if(i.isupper()):
         x = ord(i)
         x += N
         if x > ord('Z'):
            x -= NUM_LETTERS
         elif x < ord('A'):
            x += NUM_LETTERS
         y += chr(x)
      else:
         y += " "
   return y

def GoodnessFinder(S):
   y = 0
   for i in S:
      if i.isupper():
         x = ord(i)
         x -= ord('A')
         y += letterGoodness[x]
      else:
         y += 1
   return y

def GoodnessComparer(S):
   goodnesstocompare = GoodnessFinder(S)
   goodness = 0
   v = ''
   best_v = S
   for i in range(0, 26):
     v = SpyCoder(S, i)
     goodness = GoodnessFinder(v)
     if goodness > goodnesstocompare:
         best_v = v
         goodnesstocompare = goodness
   return best_v


print(GoodnessComparer(x))

Thank you for all of your help!

Answered By: Thegluestickman

I’m working on the same tutorial and used a slightly different method. This avoided creating and calling functions:

inp = input()     #to hold code text
code = list(inp)  #store code as a list
soln = []         #store the 'Goodness' for each of 26 possible answers
y=0               #variable to hold total goodness during calculations
clear = []        #will hold decoded text
pos=0             #position marker for a list

#for every possible value of shift
#note range as 0 to 25 are valid shifts and shift 26 = shift 0

for shift in range(0,26):
   for i in code:                  #loop through each letter in code
      if i == " ":                 #spaces have no score so omit them
         continue
      else:                        #if it's a letter
         x = ord(i)-shift          #apply the test shift
         if x < 65:                #prevent shifting outside A-Z range
            x = x + 26             
         x = x - 64                #turn ord into character position in A-Z with A=1
         x = letterGoodness[x-1]   #turn this into the Goodness score
         y = y + x                 #add this to a running total
   soln.insert(shift-1,y)          #AFTER decoding all letters in code, add total(y) to list of scores
   y = 0                           #reset y before next test value

bestSoln=max(soln)                 #find highest possible score

for i in range(0,26):              #check the list of solutions for this score
   if soln[i]==bestSoln:           #the position in this list is the shift we need
       bestShift = i+1             #+1 as the first solution is 0

for i in code:                     #now decode the original text using our best solution
   if i == " ":                    #spaces are not encoded so just add these to the string
      clear.insert(pos," ")        #pos used to track next position for final string
      pos = pos + 1
      continue
   else:
      x = ord(i)-bestShift         #same operation as before
      if x < 65:
         x = x + 26
   z = chr(x)
   clear.insert(pos,z)             #add the decoded letter to the clear text
   pos = pos + 1
print("".join(clear))              #join the list of clear text into one string and print it

Note that many parts of this code could (and should) be compressed, for example

x = x - 64
x = letterGoodness[x-1]
y = y + x

They are left expanded to ‘show my working’ for a tutorial exercise.

Answered By: CJC
letterGoodness = {'A': 8.17, 'B': 1.49, 'C': 2.78, 'D': 4.25, 'E': 12.70,
                  'F': 2.23, 'G': 2.02, 'H': 6.09, 'I': 6.97, 'J': 0.05, 
                  'K': 0.77, 'L': 4.02, 'M': 2.41, 'N': 6.75, 'O': 7.51, 
                  'P': 1.93, 'Q': 0.09, 'R': 5.99, 'S': 6.33, 'T': 9.06, 
                  'U': 2.76, 'V': 0.98, 'W': 2.36, 'X': 0.15, 'Y': 1.97, 
                  'Z': 0.07} 
                  
# em - input message
em = str(input()) 
em = em.upper()

# dm - guess message
dm = '' 
dmList = []
goodnessList = []
shift = 1

for g in range(25):
   goodness = 0
   dm = ''
   for i in em:
       newL = chr(ord(i) - shift)
       if i == ' ':
            dm += i

       elif ord(newL) < 65:
               A = ord(newL) + 90
               newL = chr(A - 64)
               dm += newL

       else:
            dm += newL

   for h in dm:
      if h != ' ':
         goodness += letterGoodness[h]
   goodnessList.append(goodness)
         
   shift += 1
   dmList.append(dm)  

highestG = max(goodnessList)
dmind = goodnessList.index(highestG)
print(dmList[dmind])
Answered By: sprite
def start():
   message = input()                                        ## collect user input
   maxGoodness = 0                                          ## stablish a benchmark
   maxGoodnessMessage = message                             ## use current message as baseline
   for i in range(0,26):                                    ## for each range of 1-26 the range of the alphabet and goodness
      currentMessage = decodeMesssage(message, i)           ## decode the message for the current i
      currentGoodness = calculateGoodness(currentMessage)   ##  calculate the goodness for decoded message
      if currentGoodness > maxGoodness:                     ## compare goodness to last iteration, if greater
         maxGoodness = currentGoodness                      ## update maxGoodness
         maxGoodnessMessage = currentMessage                ## store decipher message with current max goodness
         
   print(maxGoodnessMessage)

def decodeMesssage(message, S):                             ## decode message
   newMessage = ''                                          ## start with an empty message 
   for letter in message:                                   ## for each letter in the message 
      if ord(letter) == 32:                                 ## if we get an empty character just add it to the message and continue to next iteration
        newMessage = newMessage + letter
        continue
      currentAscii = ord(letter) + S                        ## calculate the value of the current letter and add the S= value
      if currentAscii > 90:                                 ## if the value of current letter + S is 90 meaning letter Z
         currentAscii = currentAscii-26                     ## find next letter starting from A
      newMessage += str(chr(currentAscii))                  ## transform back to a letter and add to message
   return newMessage 
 
def calculateGoodness(message): 
   total = 0
   for char in message:                                     ## for each character in the message
      if char == ' ':                                       ## ignore empty characters
         continue
      i = ord(char)
      total += letterGoodness[i-65]                        ## calculate the  value of the character and add it to total
   return total
      
   
   
start()
Answered By: jenn
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.