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?