In my high school class I have been assigned the task of creating a keyword cipher. However when I run it through a python visualizer I still can not see what it is doing wrong.
Here is my code so far:
n = 0
l = 0
string = ""
message = input("enter your sentence: ")
message = (message.lower())
keyword = input("enter keyword: ")
keyword = (keyword.lower())
listkw = list(keyword)
def change(message,listkw,n,string,l):
try:
for letter in message:
if letter == " ":
string += " "
else:
temp = listkw[n]
mtemp = ord(letter)
mtemp -= 96
ktemp = ord(temp)
ktemp -= 96
letterans = mtemp+ktemp
if letterans >= 27:
letterans -= 26
letterans += 96
ans = chr(letterans)
string += ans
print (string)
n+=1
message = message[l:]
l+=1
except IndexError:
n= 0
change(message,listkw,n,string,l)
change(message,listkw,n,string,l)
print (string)
When I run it with the following input
enter your sentence: computingisfun
enter keyword: gcse
it should print jrfubwbsnllkbq, because it gets the place in the alphabet for each letter adds them up and print that letter.
For example:
change('a', list('b'), 0, "", 0)
prints out c because a = 1 and b = 2 and a+b = 3 (which is (c))
But it prints out jrfupqzn, which is not at all what I expected.
I understand that you're in high school so I replace some piece of code that you write unnecessarily, you'll do better with experience ;)
First of all, you must know that it isn't a good idea programming based in an exception, is better if you add a condition and you reinitialize your n value so the exception it isn't necessary; n = n + 1 if n + 1 < len(listkw) else 0
Then, you have a little problem with the scope of the variables, you set string = "" at the start of your script but when call the function the string inner the function has a different scope so when you print(string) at the end you have an empty string value, so, the values that you use into the function like n, l and string it's better if you define inside the function scope and finally return the desired value (calculated (cipher) string)
So, the code it's something like this:
Read and initialize your required data:
message = input("enter your sentence: ").lower()
keyword = input("enter keyword: ").lower()
listkw = list(keyword)
Define your function:
def change(message,listkw):
n = l = 0
string = ""
for letter in message:
if letter == " ":
string += " "
else:
temp = listkw[n]
mtemp = ord(letter) - 96
ktemp = ord(temp) - 96
letterans = mtemp + ktemp
if letterans >= 27:
letterans -= 26
letterans += 96
string += chr(letterans)
message = message[l:]
l+=1
n = n + 1 if n + 1 < len(listkw) else 0
return string
Call and print the return value at the same time ;)
print(change(message,listkw))
You've got quite a few problems with what you're doing here. You're mixing recursion, iteration, and exceptions in a bundle of don't do that.
I think you may have had a few ideas about what to do, and you started down one track and then changed to go down a different track. That's not surprising, given the fact that you're a beginner. But you should learn that it's a good idea to be consistent. And you can do this using recursion, iteration, slicing, or driven with exceptions. But combining them all without understanding why you're doing it is a problem.
Design
Let's unwind your application into what you actually are trying to do. Without writing any code, how would you describe the steps you're taking? This is what I would say:
For every letter in the message:
take the next letter from the keyword
combine the numeric value of the two letters
if the letter is beyond Z(ebra), start back at A and keep counting
when we reach the last letter in the keyword, loop back to the beginning
This gives us a hint as to how we could write this. Indeed the most straightforward way, and one that you've got partially done.
Iteratively
Here's another pointer - rather than starting of with a dynamic problem, let's make it pretty static:
message = 'computing is awesome'
for letter in message:
print(letter)
You'll see that this prints out the message - one character per line. Great! We've got the first part of our problem done. Now the next step is to take letters from the key. Well, let's put a key in there. But how do we iterate over two strings at a time? If we search google for python iterate over two sequences, the very first result for me was How can I iterate through two lists in parallel?. Not bad. It tells us about the handy dandy zip function. If you want to learn about it you can search python3 zip or just run >>> help(zip) in your REPL.
So here's our code:
message = 'computing is awesome'
keyword = 'gcse'
for letter, key in zip(message, keyword):
print(letter, key)
Now if we run this... uh oh!
c g
o c
m s
p e
Where's the rest of our string? It's stopping after we get to the end of the shortest string. If we look at the help for zip, we see:
continues until the shortest iterable in the argument sequence is exhausted
So it's only going to go until the shortest thing. Well that's a bummer. That means we need to have a key and message the same length, right? Or does it? What if our key is longer than the message? Hopefully by now you know that you can do something like this:
>>> 'ab'*10
'abababababababababab'
If we make sure that our key is at least as long as our message, that will work. So we can just multiply the key times the number of letters in our message. I mean, we'll have way more than we need, but that should work, right? Let's try it out:
message = 'computing is awesome'
keyword = 'gcse'*len(message)
for letter, key in zip(message, keyword):
print(letter, key)
Sweet! It worked!
So now let's try just adding the ord values and let's see what we get:
for letter, key in zip(message, keyword):
print(chr(ord(letter)+ord(key)))
Oh.. dear. Well those aren't ASCII letters. As you've already found out, you need to subtract 96 from each of those. As it turns out because math, you can actually just subtract 96*2 from the sum that we've already got.
for letter, key in zip(message, keyword):
if letter == ' ':
print()
else:
new_code = (ord(letter)+ord(key)-96*2)
print(chr(new_code+96))
But we've still got non-alpha characters here. So if we make sure to just bring that value back around:
for letter, key in zip(message, keyword):
if letter == ' ':
print()
else:
new_code = (ord(letter)+ord(key)-96*2)
if new_code > 26:
new_code -= 26
print(chr(new_code+96))
Now we're good. The only thing that we have left to do is combine our message into a string instead of print it out, and stick this code into a function. And then get our input from the user. We're also going to stick our key-length-increasing code into the function:
def change(message, keyword):
if len(keyword) < len(message):
keyword = keyword * len(message)
result = ''
for letter, key in zip(message, keyword):
if letter == ' ':
result += ' '
else:
new_code = (ord(letter)+ord(key)-96*2)
if new_code > 26:
new_code -= 26
result += chr(new_code+96)
return result
message = input('enter your sentence: ')
keyword = input('enter your keyword: ')
print(change(message, keyword))
Recursion
So we've got it working using iteration. What about recursion? You're definitely using recursion in your solution. Well, let's go back to the beginning, and figure out how to print out our message, letter by letter:
message = 'computing is awesome'
def change(message):
if not message:
return
print(message[0])
change(message[1:])
change(message)
That works. Now we want to add our key. As it turns out, we can actually do the same thing that we did before - just multiply it:
def change(message, keyword):
if not message:
return
if len(keyword) < len(message):
keyword = keyword*len(message)
print(message[0], keyword[0])
change(message[1:], keyword[1:])
Well that was surprisingly simple. Now let's print out the converted value:
def change(message, keyword):
if not message:
return
if len(keyword) < len(message):
keyword = keyword*len(message)
new_code = (ord(message[0])+ord(keyword[0])-96*2)
if new_code > 26:
new_code -= 26
print(chr(new_code+96))
change(message[1:], keyword[1:])
Again we need to handle a space character:
def change(message, keyword):
if not message:
return
if len(keyword) < len(message):
keyword = keyword*len(message)
if message[0] == ' ':
print()
else:
new_code = (ord(message[0])+ord(keyword[0])-96*2)
if new_code > 26:
new_code -= 26
print(chr(new_code+96))
change(message[1:], keyword[1:])
Now the only thing that's left is to combine the result. In recursion you usually pass some kind of value around, and we're going to do that with our result:
def change(message, keyword, result=''):
if not message:
return result
if len(keyword) < len(message):
keyword = keyword*len(message)
if message[0] == ' ':
result += ' '
else:
new_code = (ord(message[0])+ord(keyword[0])-96*2)
if new_code > 26:
new_code -= 26
result += chr(new_code+96)
return change(message[1:], keyword[1:], result)
print(change(message, keyword))
Slicing
We used some slicing in our recursive approach. We even could have passed in the index, rather than slicing off parts of our string. But now we're going to slice and dice. It's going to be pretty similar to our recursive solution:
def change(message, keyword):
if len(keyword) < len(message):
keyword = keyword*len(message)
while message:
print(message[0], keyword[0])
message = message[1:]
keyword = keyword[1:]
When you see that, it shouldn't be much of a stretch to realize that you can just put in the code from our recursive solution:
while message:
if message[0] == ' ':
print()
else:
new_code = (ord(message[0])+ord(keyword[0])-96*2)
if new_code > 26:
new_code -= 26
print(chr(new_code+96))
message = message[1:]
keyword = keyword[1:]
And then we just combine the characters into the result:
def change(message, keyword):
if len(keyword) < len(message):
keyword = keyword*len(message)
result = ''
while message:
if message[0] == ' ':
result += ' '
else:
new_code = (ord(message[0])+ord(keyword[0])-96*2)
if new_code > 26:
new_code -= 26
result += chr(new_code+96)
message = message[1:]
keyword = keyword[1:]
return result
Further Reading
You can do some nicer things. Rather than the silly multiplication we did with the key, how about itertools.cycle?
What happens when you use modulo division instead of subtraction?
Related
Working on a Vigniere Decoder in Python 3 but after processing punctuation marks (which output correctly) the decoding algorithm is thrown off, I assume this to be due to incorrect drawing of comparative index values (between encoded message string and repeated key phrase string).
To get around this I thought of including a punctuation counter for correction, but I do not know where in the maths of my nested if loop to implement this. Or if this (punctuation counter) is even a fix that will work in the method of logic I am using.
I'm only working with basic tools in Python as this is supposed to be a challenge of what I've learnt so far, so simpler/more advanced ways to program a Vigniere decoder might not be something I know, in case anyone sees my attempt and thinks what the hell are they doing!
import string
alphabet = (string.ascii_lowercase)
punctuation = (string.punctuation)
def vignieredecoder(message, key):
keycompare = ''
index = 0
decodedmessage = ''
#makes key repeat to length of message w/ spaces integrity the same
for character in message:
if character == ' ':
keycompare += ' '
else:
keycompare += key[index % len(key)]
index += 1
key = keycompare
# decoding loop
for characters in range(len(message)):
punctcount = 0
if message[characters] in punctuation:
decodedmessage += message[characters]
punctcount += 1
elif message[characters] == ' ':
decodedmessage += ' '
else:
# store index value of character in message, e.g. a = 0, b = 1, etc
messageletterindexval = alphabet.index(message[characters])
# store index value of character in keycompare[place of character in message], e.g. if key is friends and first iteration being 0 = f
keycompareindex = key[characters]
# access character in alphabet at indexvalue of keycompare, e.g. if key is friends and first iteration of for loop, finds f in alphabet and returns indexval 5
keycompareindexval = alphabet.index(keycompareindex)
decodedletterval = messageletterindexval - keycompareindexval
if decodedletterval < 0:
decodedletterval += 26
decodedmessage += alphabet[decodedletterval]
else:
decodedmessage += alphabet[decodedletterval]
return decodedmessage
print(vignieredecoder('dfc aruw fsti gr vjtwhr wznj? vmph otis! cbx swv jipreneo uhllj kpi rahjib eg fjdkwkedhmp!', 'friends' ))
terminal output: you were able to decode this? rzmp jcao! zjs bor wfxmnfab rpgub gcf zvqbeo bo asvgjhmyqel!
expected output: you were able to decode this? nice work! you are becoming quite the expert at crytography!
I am trying to capitalize every other letter of a string which is given by and input. For some reason it give me the error 'string index out of range' and i have no idea why! the range is set from 0 to the length of the string so that cant be possible i thought!
s = input('Please enter a string: ')
p=s.lower()
o=s.upper()
q=p
k=len(s)
l=1
for x in range(0,k):
if l%2==0:
q=q[x].swapcase()
l+=1
else:
l+=1
print(q)
When you do this:
q=q[x].swapcase()
q becomes a single letter.
The next time around you try:
q[1]
but there is no q[1] because you made it a single letter.
This is one of several reasons why python encourages you to avoid creating index variables and instead looping over the items themselves. If you do that and give your variables more descriptive names, these kind of error are easier to catch. For example:
s = input('Please enter a string: ')
lower_case = s.lower()
new_string = ""
for index, letter in enumerate(lower_case):
if index % 2 == 0:
new_string += letter.swapcase()
else:
new_string += letter
print(new_string)
There is some issue with my python code. I am making a program that finds the occurrences of the letter A in a word and if that letter is found and the next letter is not the letter A the A is swapped with the next letter.
As an example TAN being TNA but WHOA staying as WHOA
AARDVARK being ARADVRAK
The issue is when I input ABRACADABRA I get a string index out of range exception. Before I had that exception I had the word that prints it as BRACADABRIi'm not sure why if I have to add another loop in my program.
If you guys also have anymore efficient way to run the code then the way I have please let me know!
def scrambleWord(userInput):
count = 0
scramble = ''
while count < len(userInput):
if userInput[count] =='A' and userInput[count+1] != 'A':
scramble+= userInput[count+1] + userInput[count]
count+=2
elif userInput[count] != 'A':
scramble += userInput[count]
count+=1
if count < len(userInput):
scramble += userInput(len(userInput)-1)
return scramble
#if a is found switch the next letter index with a's index
def main():
userInput = input("Enter a word: ")
finish = scrambleWord(userInput.upper())
print(finish)
main()
When you get to the end of the string and it is an 'A' your program is then asking for the next character which is off the end of the string.
Change the loop so it doesn't include the last character:
while count < len(userInput)-1:
if ...
You can modify your code as below:
def scrambleWord(userInput):
count = 0
scramble = ''
while count < len(userInput):
if count < len(userInput)-1 and userInput[count] =='A' and userInput[count+1] != 'A':
scramble+= userInput[count+1] + userInput[count]
count+=2
else:
scramble += userInput[count]
count+=1
return scramble
You are not checking the condition (count < len(userInput)-1) when logic tries to check for A's occurrence and swap with next letter. It throws string index out of range exception.
The issue arises in your code when last character in input is 'A'.
This is because your first if in the loop tries to access 'count + 1' character during last iteration.
And since there's no character at that position, you get index error.
The simplest solution would be to make a separate if condition for the same.
Updated snippet for while loop might look like this -
# while start
while count < len_: # len_ is length of input
if count + 1 >= len_:
break # break outta loop, copy last character
current = inp[count]
next_ = inp[count + 1]
if current == 'A':
op += ( next_ + current) # op is result
count += 1
else:
op += current
# increment counter by 1
count += 1
# rest of the code after while is same
Another small issue in your code is while copying last character ( after loop ends ), you should use [ ] instead of ( ) to refer last character in input string.
Just for fun :
from functools import reduce
def main():
word = input("Enter a word: ").lower()
scramble = reduce((lambda x,y : x[:-1]+y+'A' \
if (x[-1]=='a' and y!=x[-1]) \
else x+y),word)
print(scramble.upper())
main()
I am currently working on a cipher program for a beginners python course. We first were told to create a function that would return the position of a given letter, using a string of the alphabet as a reference (that is my alphabet_position function.) Next, we were told to make a function that would allow for a single letter to be rotated by a chosen number(that is my rotate_character function). Third, we were tasked with creating a basic caesar cipher using the previous two functions. All of those I was able to make work as demonstrated by my code below.
The vigenere, however, is proving much more difficult. I was actually able to find a snippet of code that I was able to modify with my first function (alphabet_position) to make work if only alphabetic characters are used, but as soon as i enter any non alphabetical character (such as ! or ?) I get a return of ValueError: Substring Not found. When the program encounters these non alphabetical characters, the key is supposed to skip over them and carry the Nth character of the key to the next alphabetical character.
I am guessing the answer lies in somehow incorporating my rotate_character function into my Encrypt function, but I am unsure how to do this, as the rotate_character function expects a alphabetical character, and the vigenere function turns that parameter into an int before running it through.
Any advice? And as I am a new programmer, I will gladly take any other helpful criticism on my coding practices you may want to instill!`
> #Create function alphabet_position(letter) to turn letter into number
> #such as a=0 or e=4, using lowercase to make sure case doesnt matter.
alphabet = "abcdefghijklmnopqrstuvwxyz"
def alphabet_position(letter):
> lower_letter = letter.lower() #Makes any input lowercase.
> return alphabet.index(lower_letter) #Returns the position of input
as a number.
>
> def rotate_character(char, rot):
> if char.isalpha():
> a = alphabet_position(char);
> a = (a + rot) % (int(len(alphabet))); #needs modulo
> a = (alphabet[a]);
> if char.isupper():
> a = a.title()
> return a
> else:
> return char
>
> def caesar(text, rot):
> list1 = ""
> for char in text:
> list1 += rotate_character(char, rot)
> return list1
>
> def vigenere(text,key):
m = len(key)
>
> newList = ""
>
> for i in range(len(text)):
text_position = alphabet_position(text[i])
key_position = alphabet_position(key[i % m])
value = (text_position + key_position) % 26
newList += alphabet[value]
return newList
>
> def main():
> x = input("Type a message: ")
> y = input("Rotate by: ")
> result = vigenere(x, y)
> print (result)
>
> if __name__ == '__main__':
main()
No, you don't need the rotate function anymore. You just need to directly add any character that is not in the alphabet to newlist and then skip the encryption part.
Now a sub-optimal way of doing this is to use if ... in ...:
if text[i] in alphabet:
# do your thing
else:
newList += text[i]
Of course more optimal is to only go through the alphabet once and use a variable:
pt_c = text[i]
pt_i = alphabet.find(pt_c) # returns -1 instead of an error when not found
if pt_i == -1:
newList += pt_c
else:
newList += pt_c
# do your thing *with the given index*
This won't make any difference in the runtime for a Vigenère cipher of course. But it shows you how to think of efficient programming for later: there is no need to search twice.
You could also continue the loop instead of having an else statement:
pt_c = text[i]
pt_i = alphabet.find(pt_c) # returns -1 instead of an error when not found
if pt_i == -1:
continue
# do your thing with the given index
this will make the indentation depth of your loop (the amount of scopes) less, with the unfortunate side effect of making your loop more complex (creating a local exit point).
This is a module in my program:
def runVowels():
# explains what this program does
print "This program will count how many vowels and consonants are"
print "in a string."
# get the string to be analyzed from user
stringToCount = input("Please enter a string: ")
# convert string to all lowercase letters
stringToCount.lower()
# sets the index count to it's first number
index = 0
# a set of lowercase vowels each element will be tested against
vowelSet = set(['a','e','i','o','u'])
# sets the vowel count to 0
vowels = 0
# sets the consonant count to 0
consonants = 0
# sets the loop to run as many times as there are characters
# in the string
while index < len(stringToCount):
# if an element in the string is in the vowels
if stringToCount[index] in vowels:
# then add 1 to the vowel count
vowels += 1
index += 1
# otherwise, add 1 to the consonant count
elif stringToCount[index] != vowels:
consonants += 1
index += 1
# any other entry is invalid
else:
print "Your entry should only include letters."
getSelection()
# prints results
print "In your string, there are:"
print " " + str(vowels) + " vowels"
print " " + str(consonants) + " consonants"
# runs the main menu again
getSelection()
However, when I test this program, I get this error:
line 28, in runVowels
stringToCount = input("Please enter a string: ")
File "<string>", line 1
PupEman dABest
^
SyntaxError: unexpected EOF while parsing
I tried adding a + 1 to the "while index < len(stringToCount)" but that didn't help either. I'm pretty new to python and I don't really understand what's wrong with my code. Any help would be appreciated.
I researched this error, all I found out was that EOF stands for end of file. This didn't help at all with resolving my problem. Also, I understand that sometimes the error isn't necessarily where python says the error is, so I double-checked my code and nothing seemed wrong in my eyes. Am I doing this the round-about way by creating a set to test the string elements against? Is there a simpler way to test if string elements are in a set?
Question resolved. Thank you to all!
Looks like you're using Python 2. Use raw_input(...) instead of input(...). The input() function will evaluate what you have typed as a Python expression, which is the reason you've got a SyntaxError.
As suggested use raw_input. Also you don't need to do this:
while index < len(stringToCount):
# if an element in the string is in the vowels
if stringToCount[index] in vowels:
# then add 1 to the vowel count
vowels += 1
index += 1
# otherwise, add 1 to the consonant count
elif stringToCount[index] != vowels:
consonants += 1
index += 1
# any other entry is invalid
else:
print "Your entry should only include letters."
getSelection()
Strings in Python are iterable, so you can just do something like this:
for character in stringToCount:
if character in vowelSet : # Careful with variable names, one is a list and one an integer, same for consonants.
vowels += 1
elif character in consonantsSet: # Need this, if something is not in vowels it could be a number.
consonants += 1
else:
print "Your entry should only include letters."
This should do just fine. Using a while is not necessary here, and very non-Pythonic imho. Use the advantage of using a nice language like Python when you can to make your life easier ;)
You can count the vowels like so:
>>> st='Testing string against a set of vowels - Python'
>>> sum(1 for c in st if c.lower() in 'aeiou')
12
You can do something similar for consonants:
>>> sum(1 for c in st if c.lower() in 'bcdfghjklmnpqrstvwxyz')
26
Also,
if stringToCount[index] in vowels:
should read
if stringToCount[index] in vowelSet:
Here's another way you could solve the same thing:
def count_vowels_consonants(s):
return (sum(1 for c in s if c.lower() in "aeiou"),
sum(1 for c in s if c.lower() in "bcdfghjklmnpqrstvwxyz"))
To wit:
>>> count_vowels_consonants("aeiou aeiou yyy")
(10, 3)
>>> count_vowels_consonants("hello there")
(4, 6)
Python truly is grand.
The errors in your file run as follows (plus some suggestions):
stringToCount = input("Please enter a string: ")
This should be raw_input if you want what the user typed in as a string.
stringToCount.lower()
The .lower() method returns a new string with its letters lowered. It doesn't modify the original:
>>> a = "HELLO"
>>> a.lower()
"hello"
>>> a
"HELLO"
vowelSet = set(['a','e','i','o','u'])
Here you could just as easily do:
vowelSet = set("aeiou")
Note you also don't strictly need a set but it is indeed more efficient in general.
# sets the vowel count to 0
vowels = 0
# sets the consonant count to 0
consonants = 0
Please, you don't need comments for such simple statements.
index = 0
while index < len(stringToCount):
You usually don't need to use a while loop like this in python. Note that all you use index for is to get the corresponding character in stringToCount. Should instead be:
for c in stringToCount:
Now instead of:
if stringToCount[index] in vowels:
vowels += 1
index += 1
You just do:
if c in vowels:
vowels += 1
elif stringToCount[index] != vowels:
consonants += 1
index += 1
# any other entry is invalid
Not quite right. You're checking that a character doesn't equal a set. Maybe you meant:
elif c not in vowels:
consonants += 1
But then there'd be no else case... Got to fix your logic here.
print "In your string, there are:"
print " " + str(vowels) + " vowels"
print " " + str(consonants) + " consonants"
The above is more pythonically written as:
print "In your string, there are: %s vowels %s consonants" % (
vowels, consonants)
# runs the main menu again
getSelection()
Not sure why you're calling that there - why not call getSelection() from whatever calls runVowel()?
Hope that helped! Enjoy learning this great language.
Bah, all that code is so slow ;). Clearly the fastest solution is:
slen = len(StringToCount)
vowels = slen - len(StringToCount.translate(None, 'aeiou'))
consonants = slen - vowels
...note that I don't claim it's the clearest... just the fastest :)