Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Caesar cipher
#1
I’ve begun writing a basic Caesar cipher as part of a Udemy course by Jose Portilla that I am taking for fun.

I am writing the encryption function. Jose has included some helpful pseudo code and a doc string to sort of get his students started.

Here is the docstring and some pseudo code provided by the instructor:

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''
    
    # Create a normal plain alphabet
    
    # Create a shifted version of this alphabet 
    # (Try slicing using the shift and then reconcatenating the two parts)
    
    # Use a for loop to go through each character in the original message.
    # Then figure out its index match in the shifted alphabet and replace.
    # It might be helpful to create an output variable to hold the new message.
    
    # Keep in mind you may want to skip punctuation with an if statement.
    
    # Return the shifted message. Use ''.join() method 
    # if you still have it as a list.
            
    pass
Here is my work in progress:
from collections import deque
import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''  
    alphab = string.ascii_lowercase
    print(alphab)    
    alphab = deque(list(alphab))
    print(alphab)
    alphab.rotate(shift)
    print(alphab)
    alphab = ''.join(alphab)
    print(alphab)
    
    pass
When I call the function with encrypt(None,3), it produces this as output:
Quote:abcdefghijklmnopqrstuvwxyz
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'])
deque(['x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'])
xyzabcdefghijklmnopqrstuvw
That demonstrates that I have some of the basic functionality in place and working.

The issue I’ve encountered is when I attempt to add the for loop between lines 14 and 15 so my script now looks like this:
from collections import deque
import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''    
    alphab = string.ascii_lowercase
    print(alphab)    
    alphab = deque(list(alphab))
    print(alphab)
    alphab.rotate(shift)
    print(alphab)
    for index, alphab in enumerate(text):
        # ?????????? I"m not sure how to proceed, ugh
        print(index, text)
    alphab = ''.join(alphab)
    print(alphab)
    
    pass
When I call the function with encrypt('Helter Skelter! Feed the birds and deploy the vicious little piggies!',3), that produces this:
Quote:abcdefghijklmnopqrstuvwxyz
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'])
deque(['x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'])
0 Helter Skelter! Feed the birds and deploy the vicious little piggies!
1 Helter Skelter! Feed the birds and deploy the vicious little piggies!
2 Helter Skelter! Feed the birds and deploy the vicious little piggies!
3 Helter Skelter! Feed the birds and deploy the vicious little piggies!
4 Helter Skelter! Feed the birds and deploy the vicious little piggies!
5 Helter Skelter! Feed the birds and deploy the vicious little piggies!


. . .


65 Helter Skelter! Feed the birds and deploy the vicious little piggies!
66 Helter Skelter! Feed the birds and deploy the vicious little piggies!
67 Helter Skelter! Feed the birds and deploy the vicious little piggies!
68 Helter Skelter! Feed the birds and deploy the vicious little piggies!
!

The problem clearly is with my enumeration loop. What is actually happening in the loop as it appears now is this: for every item in the string (the string passed into the function - - there are 70 or so characters in total), it’s printing the full text. That’s not at all what I want. I want to apply the rotated alphabet for each character in the string. I am really struggling. Can anyone provide a little advice without completing it all for me?

I’ve had some previous help with enumeration on this forum in my thread titled “Printing lines in a basic text file”. Stack Overflow’s “What does enumerate mean?” is helpful too.

The course material I am working with can be found on Jose Portilla’s GitHub page. Here is the repo’s top level directory. The module I am working on is #6 called “Hacking incident”.
Reply
#2
Ok, so I'd suggest having two different variables of the string.ascii_lowercase. One that you shift, and one that you leave unchanged. That way, while looping through the text you're changing, you can look the character up in the original, and find the new character in the shifted version.

And once that's done, there's some other things we can do. But baby steps :)
Reply
#3
Funny thing. I did not know that deque supports rotate.
You can also work with translate. I'm not sure if it's in deprecation process.
The funny part is, that you don't need to program your own for-loop.
But it's a different way and does not answer your question.

Maybe it's faster and maybe the developers remove this method.
But we got also the methods for bytes back, which is very handy.
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#4
Thank you, @nilamo, for your help so far.

I’ve got a script which prints the original alphabet and an alphabet rotated by a factor of three. Here it is:

from collections import deque
import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caesar cipher.
    ''' 
    alphabet = string.ascii_lowercase # Initializing alphabet variable
    print(alphabet) 
    alphabet = deque(list(alphabet)) # Turning alphabet into a list
    alphabet_shifted = alphabet.rotate(shift) # Assigning new variable to 
    alphabet = ''.join(alphabet) # Re-concatenating split list
    print(alphabet)
    print(alphabet_shifted)
    pass
That script produces the desired output:

Quote:abcdefghijklmnopqrstuvwxyz
xyzabcdefghijklmnopqrstuvw
None

Mission accomplished, right? Not quite in this case because there are some issues and quirks present that I can’t explain such as:
  1. If I comment out line 10, the alphabet variable should still print because I tell the script to do so again at line 14. But when commenting out that line, the output doesn't appear.
  2. If I un-comment line 10 and comment out line 14, I’d expect ‘xyzabcdefghijklmnopqrstuvw’ to not print at all in the output but instead the original alphabet prints and alphabet_shifted does not.
  3. If I comment out line 15, None disappears from the output. Un-commenting this line, None re-appears.
  4. I only reassemble the alphabet list using the .join method. The alphabet_shifted list should still be a list with each character separated by a comma. Why does the shifted alphabet appear concatenated in the output when I didn’t tell the script to do this yet?
I have no idea wtf is going on here. The last one there bothers me the most.

I figure I should prolly just nuke my script from orbit and start over. I’m think it’s the double ended que method that I am misusing and misunderstanding. Based on answers to a StackOverflow question on the ways of shifting a list in Python, here is my fresh approach to the Caesar cipher:

import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''
    text = None
  
    alphabet = string.ascii_lowercase #Generate alphabet
    print(alphabet)
    alphabet = alphabet.split() # Split string into list
    alphabet_shifted = alphabet.append(alphabet.pop(shift)) # Declare shifted
    print(alphabet_shifted)    
            
    pass
Based on your feedback, @nilamo, I feel that this alternate approach more clearly and concisely declares two separate alphabets - - the original and a shift of the original. As far as I can tell, the syntax, casting, and method calls above are ‘airtight’. Yet when I invoke the function encrypt(None,2), I get this as my output:

Quote:abcdefghijklmnopqrstuvwxyz

---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-9-dceee40e0379> in <module>()
----> 1 encrypt(None,2)

<ipython-input-8-5eb7d70773cb> in encrypt(text, shift)
11 print(alphabet)
12 alphabet = alphabet.split()
---> 13 alphabet_shifted = alphabet.append(alphabet.pop(shift))
14 print(alphabet_shifted)
15

IndexError: pop index out of range

Why am I getting this output?

I Googled ‘pop index out of range’ and similar search terms and encountered a StackOverflow question titled, “Pop index out of range”. But I can’t see how the StackOverflow answers there address the issue with the .pop method in my script.

@DeaD_EyE: I appreciate your post but it is beyond me at this point with my novice experience with Python. I'm sure it will make sense to me eventually.
Reply
#5
I did some tests and found your problem. The first thing I noticed was your output (which matched mine):

Quote:abcdefghijklmnopqrstuvwxyz
xyzabcdefghijklmnopqrstuvw
None

Since your code calls print() three times and we get three printed items, they must be all three items printed in order. So, the second item is not alphabet_shifted; instead, it's alphabet being printed on line 14. To verify that, I did this:

from collections import deque
import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caesar cipher.
    '''
    alphabet = string.ascii_lowercase # Initializing alphabet variable
    print(alphabet, 1)
    alphabet = deque(list(alphabet)) # Turning alphabet into a list
    alphabet_shifted = alphabet.rotate(shift) # Assigning new variable to
    alphabet = ''.join(alphabet) # Re-concatenating split list
    print(alphabet, 2)
    print(alphabet_shifted)
    pass
    
encrypt("Hello again", 5)
Which produced:

Quote:abcdefghijklmnopqrstuvwxyz 1
vwxyzabcdefghijklmnopqrstu 2
None

The deque.rotate() method is altering the deque without providing an output. So, when you call alphabet.rotate(), you're changing alphabet. Because that method does not have a return, alphabet_shifted is instantiated as None which is why the third call to print prints "None".
Reply
#6
(Oct-27-2018, 12:20 AM)stullis Wrote: The deque.rotate() method is altering the deque without providing an output. So, when you call alphabet.rotate(), you're changing alphabet.

I’m having an ‘ah-ha’ moment here. Like you’ve said, rotate() is not a function. It’s a method. It modifies the attribute of the object in its place. So it’s pointless to assign it as a variable. I’ve modified my script (below) by duplicating the original alphabet and the shifted alphabet. Then I proceed to manipulate the shifted alphabet in its place. Here is my code now:

from collections import deque
import string

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caesar cipher.
    ''' 
    original_alphabet = string.ascii_lowercase # Initializing alphabet variable
    print(original_alphabet, 1) 
    original_alphabet = deque(list(original_alphabet)) # Turning alphabet into a list
    shifted_alphabet = original_alphabet 
    shifted_alphabet.rotate(shift) # Rotating new shifted alphabet
    original_alphabet = ''.join(original_alphabet) # Re-concatenating split list (alphabet)
    shifted_alphabet = ''.join(shifted_alphabet) # Re-concatenating split list (shifted alphabet)
    print(original_alphabet, 2)
    print(shifted_alphabet, 3)
    pass
encrypt(None,3)
Here is the output:

Quote:abcdefghijklmnopqrstuvwxyz 1
xyzabcdefghijklmnopqrstuvw 2
xyzabcdefghijklmnopqrstuvw 3

The second print statement at line 16 is still printing the shifted alphabet when I intended to print the original. @stullis I get that you’ve already addressed this point but I don’t understand what you mean when you say that since I have three printed items, they must all be printed in order. Yes, I see the three printed statements, but I am telling the Jupyter Notebook to print the original alphabet twice and the shifted alphabet once, rather than the reverse. I am still baffled.

Would someone be able to clarify further?

By the way, passing an integer in the three instances where the print function is invoked really helps.

Thanks stullis for your contribution so far.
Reply
#7
To figure out what is going on, why is it that line 2 and 3 of the output from your latest script are identical we need to think about what a reference is verses what a copy is or what is the difference between the '==' operator and the 'is' operator. Is a==b the same as a is b

Look at this example, try to figure out what might be going on, do you really have a copy? Did you photocopy the original and thus any changes are separate? Or is it actually a clone of the original?

from collections import deque
import string

def encrypt(text,shift):
    alphab = string.ascii_lowercase
    org = deque(list(alphab))
    shifted = org
    shifted.rotate(shift)
    print(shifted is org)

encrypt('test', 3)
Reply
#8
What I meant about the "printing in order" part was that the code is calling print() three times. Because this is all on a single thread, the first print item must correlate to the first print() call, the second print to the second print() call, etc. That's how I pieced together that something was up with deque.rotate() because you printed the same variable twice and got two different results.

Knackwurstbagel has you covered on this one. The assignment of shifted_alphabet is a shallow copy so changing either the original or the copy will change both.
Reply
#9
In python, variables are like boxes with labels on them. You created one deque, but stuck two different labels on that one object. So you rotated the one deque, but both variables are still pointing to the same object. The same thing happens with lists, dicts, objects, etc.

The only things that don't work that way are ints/floats/strings, and a few other minor things. Because those are never changed in place, making any change to them creates a whole new object.
Reply
#10
Hooray! Now my original alphabet and shifted alphabet print as intended. Here is my new script:

from collections import deque
import string
import copy

def encrypt(text,shift_variance):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caesar cipher.
    ''' 
    original = string.ascii_lowercase # Initializing alphabet variable
    print(original, 1) 
    original = deque(list(original)) # Turning the original alphabet into a list
    shifted = original.copy() # Assigning new variable to copy of original alphabet
    shifted.rotate(shift_variance) # Rotating new shifted alphabet 
    original = ''.join(original) # Re-concatenating split list (alphabet)
    shifted = ''.join(shifted) # Re-concatenating split list (shifted alphabet)
    print(original, 2)
    print(shifted, 3)
    pass
At line thirteen I invoke the copy function from the copy module. I learned about this technique by Googling around for ‘python copy reference’ and similar search terms as introduced by @knackwurstbagel and @nilamo. In particular, the explanation found over in a Stackoverflow question titled “How to clone or copy a list?“ helped tremendously.

Here is my new output:

Quote:abcdefghijklmnopqrstuvwxyz 1
abcdefghijklmnopqrstuvwxyz 2
xyzabcdefghijklmnopqrstuvw 3

Now we can return to writing a for loop to loop through each character in the message entered by the user.

As a reminder, here is the original doc string and pseudo code provided by the instructor for the encrypt function:

def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''
    
    # Create a normal plain alphabet
    
    # Create a shifted version of this alphabet 
    # (Try slicing using the shift and then reconcatenating the two parts)
    
    # Use a for loop to go through each character in the original message.
    # Then figure out its index match in the shifted alphabet and replace.
    # It might be helpful to create an output variable to hold the new message.
    
    # Keep in mind you may want to skip punctuation with an if statement.
    
    # Return the shifted message. Use ''.join() method 
    # if you still have it as a list.
            
    pass
I can check off a number of these lines as already implemented. I’ve created a plain alphabet and created a shifted alphabet. I’ve returned a shifted alphabet and joined them together. Now I am going to tackle the for loop.

Here is my script:

from collections import deque
import string
import copy

def encrypt(text,shift_variance):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caesar cipher.
    ''' 
    original = string.ascii_lowercase # Initializing alphabet variable
    original = deque(list(original)) # Turning the original alphabet into a list
    shifted = original.copy() # Assigning new variable to copy of original alphabet
    shifted.rotate(shift_variance) # Rotating new shifted alphabet 

    # BEGIN for loop:
    text = list(text) # Convert text to string
    print(text) # Confirmation of conversion operation
    scrambled_text =[] # Initializing output variable 
    for index in text:
        for variable, char in enumerate(shifted):
            variable[char] = scrambled_text        
    pass
When I call the encryption function using encrypt("Hello World",3), I get this output:

Quote:['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']


---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-51-83dd904990e0> in <module>()
----> 1 encrypt("Hello World",3)

<ipython-input-50-a466565b77aa> in encrypt(text, shift_variance)
19 for index in text:
20 for variable, char in enumerate(shifted):
---> 21 variable[char] = scrambled_text
22 pass

TypeError: 'int' object does not support item assignment

What is going wrong in my script?

shifted is the list of characters in the shifted alphabet, so it’s iterable. I Googled ‘TypeError: 'int' object does not support item assignment’ which turned up a Stackoverflow question with that TypeError in the title but the explanation completely escapes me.

I'm trying to use a for loop to go through each character in the original message and then assign a new letter from the shifted alphabet to each index match in the input text. Or in other words, what I am stuck on the most here is how to come up with the algorithm (a for loop or combination of two for loops) to 'replace' one character in the input text with a letter in the shifted alphabet. When "replacing" is used by the instructor in the pseudo code, I am not sure how to translate that into Python code. This is my biggest hurdle. Can someone here provide some guidance and clarity?

Other resources I’ve used include Ned Batchelder’s talk on YouTube titled “Loop like a native: while, for, iterators, generators”.

I’ve had some previous help with enumeration on this forum in my thread titled “Printing lines in a basic text file” along with Stackoverflow’s “What does enumerate mean?”.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Ceaser cipher program oli_action 5 2,816 Sep-14-2020, 10:43 AM
Last Post: oli_action
  Cipher Caesar Azilkhan 1 2,116 Nov-21-2019, 03:40 PM
Last Post: ichabod801
  No idea how to use the Caesar Cypher in my code celtickodiak 5 3,018 Oct-08-2019, 03:29 AM
Last Post: stullis
  Monoalphabetic cipher pawlo392 1 12,677 Apr-01-2019, 08:51 PM
Last Post: ichabod801
  Vigenere and Caesar Cipher sammy2938 1 5,702 Jul-29-2017, 01:32 PM
Last Post: DeaD_EyE

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020