Posts: 218
Threads: 89
Joined: Nov 2017
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”.
Posts: 3,458
Threads: 101
Joined: Sep 2016
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 :)
Posts: 2,125
Threads: 11
Joined: May 2017
Oct-26-2018, 07:48 PM
(This post was last modified: Oct-26-2018, 07:48 PM by DeaD_EyE.)
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.
from collections import deque
from string import ascii_lowercase
from string import ascii_letters
rot13_lowercase = deque(ascii_lowercase)
rot13_lowercase.rotate(13) # returns None, inline modification of this object
rot13_lowercase = ''.join(rot13_lowercase) # fills between each element an empty str, result is a str
rot13_uppercase = rot13_lowercase.upper()
# create the translation
rot13_translation = str.maketrans(ascii_letters, rot13_lowercase + rot13_uppercase)
# rot13_translation is a dict, where the keys are the original letters (as integer) and
# the values are the rotated str as integers
# now you can use the translation on str with the method translate
my_secret = 'Hello World 123 !!!!1....??'
print(my_secret.translate(rot13_translation))
# doing the translation two times decrypts the encrypted text
print(my_secret.translate(rot13_translation))
print(my_secret.translate(rot13_translation).translate(rot13_translation))
Maybe it's faster and maybe the developers remove this method.
But we got also the methods for bytes back, which is very handy.
Posts: 218
Threads: 89
Joined: Nov 2017
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:
- 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.
- 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.
- If I comment out line 15, None disappears from the output. Un-commenting this line, None re-appears.
- 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.
Posts: 443
Threads: 1
Joined: Sep 2018
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".
Posts: 218
Threads: 89
Joined: Nov 2017
(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.
Posts: 31
Threads: 0
Joined: Dec 2017
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)
Posts: 443
Threads: 1
Joined: Sep 2018
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.
Posts: 3,458
Threads: 101
Joined: Sep 2016
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.
Posts: 218
Threads: 89
Joined: Nov 2017
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?”.
|