Additional letter printed when I am executing Decode() - python

So when I encode "hello" in my Encode() function, with a shift of three I get the result "khoor". When I attempt to decode "khoor" using my decode function with a shift of three, I get "hellor". This is strange because an extra letter "r" is returned, despite only decoding 5 letters. This happens with every string I attempt to decode, and I have noticed that the last letter of every string to be encoded is added as an additional letter to the decoded string.
ALPHABET = 'abcdefghijklmnopqrstuvwxyz'
def Menu():
print("Please choose from the following: \n")
print("'e' to encode a string.")
print("'d' to decode a string.")
print("'q' to quit.\n")
choice = input("Please enter one of the letters above.\n")
if choice == "e":
print (Encode())
if choice == "d":
print (Decode())
if choice == "q":
print("The program will now exit.")
quit()
def stringValidation():
while True:
try:
valid = str(input("Enter a string to encode.\n"))
return valid
break
except:
print("Value Error. Enter a string with only letters from the alphabet.")
continue
def shiftValidation():
while True:
try:
valid = int(input("Enter the number of shifts you would like.\n"))
return valid
break
except:
print("Value Error. Please enter an integer.")
def decodeShiftValidation():
while True:
try:
valid = int(input("Enter the key. (Number of shifts used to encrypt the encoded word.)\n"))
return valid
break
except:
print("Value Error. Please enter an integer.")
def Encode():
data = []
string = stringValidation() # asks the user for the string input to be encoded
shift = shiftValidation() # asks the user for the number of shifts
for i in string: # for the letters in string...
if i.strip() and i in ALPHABET: # i.strip removes all default whitespace characters from i (string input by user.)
data.append(ALPHABET[(ALPHABET.index(i) + shift) % 26]) # gets position of the letters from input string in ALPHABET using indexing, and adds the shift to get the new position and new letter.
else:
data.append(i) # if it is a space, simply append it to the data.
output = ''.join(data)
return output
encoded_string= Encode()
print(encoded_string)
def Decode():
data = []
string = input("Please enter the string you wish to decode.\n")
shift = int(input("Enter the key. (Number of shifts used when encoding original word. \n"))
for i in string:
if i.strip() and i in ALPHABET:
data.append(ALPHABET[(ALPHABET.index(i) - shift) % 26])
else:
data.append(i)
output = ''.join(data)
return output
Menu()

An indentation error makes the else of your Decode function a else for the for loop (which is a lesser-known feature of for loop: if no break is encountered, the else executes, adding an extra letter in your case).
That explains why you don't get an error but unexpected behaviour.
More here: Why does python use 'else' after for and while loops?
Aside, a very clumsy way to search in the alphabet is to use index, when you can compute the index directly with characters codes:
data.append(ALPHABET[(ord(i)-ord(ALPHABET[0]) + shift) % 26])
Aside #2: note that decode & encode methods are very similar. Factorize them with the shift as parameter (which is the opposite from one method to the other)

I got errors trying to run your script until I changed all of the input statements to raw_input. But I don't quite understand the logic behind if i.strip() and i in ALPHABET:. Also, you don't have to accumulate letters in a list one at a time and then join them back, when Python lets you append strings directly. In any case, when I simplified your Decode() function to the following, it worked for me:
def Decode():
string = raw_input("Please enter the string you wish to decode.\n").lower().strip()
if string.isalpha():
shift = int(raw_input("Enter the key. (Number of shifts used when encoding original word. \n"))
return ''.join([ ALPHABET[(ALPHABET.index(c) - shift) % 26] for c in string])
I also added in a .lower() and .isalpha() check in case the user uses any capital letters or non-alphabetic strings, but there are other things like this you can add to handle other use cases.

Related

How do you split duplicate letters with "x" in Python?

I am trying to create a function which is able to detect when two letters back to back are duplicates, for example the ls in "hello", and split the duplicate letters with the letter "x". Here is my code:
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
i = 0 # sets i to 0
for letter in plaintext:
if plaintext[-1] == plaintext[-2]: # if the last letter is the same as the second to last
plaintext = plaintext[:-1] + "x" + plaintext[-1:] # separate them with an x
elif plaintext[i] == plaintext [i+1]: # if one letter is the same as the next letter
# the line above makes an error
plaintext = plaintext[:i+1] + "x" + plaintext[i+1:] #separate them with an x
i += 1
else:
i += 1
This code works when I enter hello there as the input; I receive helxlothere. However, when I test another input, such as heythere, IndexError: string index out of range shows up for elif line (line 12). How can I make this code work for all inputs?
You can use regex to achieve this.
For both approaches, it will work for hello there hey there
The difference comes when more than two character repetition happens.
approach 1
import re
string='hello there'
# find Any repeated character and add x in between them
answer = re.sub(r'(.)\1{1}', r'\1x\1', string)
print(answer)
Here for hellllo there text, you will get output helxllxlo there
approach 2
alternatively, you can use this method.
s="hello there"
for match in re.finditer(r"(.)\1+", s):
old=s[match.start():match.end()]
s=s.replace(old,'x'.join(old))
print(s)
here for hellllo there text, you will get output helxlxlxlo there as output.
I think the second approach will be more appropriate.
The IndexError is caused by the fact that you are looking at plaintext[i+1]. As you can see in the word heythere, there are no letters which match back to back, and therefore the code continues until it hits the end, and so you get an IndexError because there is no element i+1.
You can fix this by using this code instead:
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
i = 0 # sets i to 0
for letter in plaintext:
if plaintext[-1] == plaintext[-2]: # if the last letter is the same as the second to last
plaintext = plaintext[:-1] + "x" + plaintext[-1:] # separate them with an x
try:
elif plaintext[i] == plaintext [i+1]: # if one letter is the same as the next letter
# the line above makes an error
plaintext = plaintext[:i+1] + "x" + plaintext[i+1:] #separate them with an x
i += 1
else:
i += 1
except IndexError:
pass
This code should stop your code from crashing in the elif statement, while also completing properly.
Hope this helps, have a nice day!
You are receiving the IndexError because during iterating, when the loop reaches the last letter,
elif plaintext[i] == plaintext [i+1]:
this line checks for the letter after the last letter, which does not exist, which causes the program to run into IndexError.
You have to check till the second last letter, for it to work properly.
A simple and easier way to arrive at the same output with a bit easier logic.
Logic
Create a new string and insert all letters which are in the old string, plaintext, to the new string, newPlainText, and check only for one condition that is whether the last letter is same as current or not and if yes then also insert letter 'x' into the newPlainText and that's it!
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
newPlainText = plaintext[0] # copy the first letter of plaintext to the new string which will handle the duplicates and insert `x` between them
i = 1 # sets i to 1
while i < len(plaintext):
if plaintext[i] == plaintext[i-1]: # we check that if prev and current letters are same then also append x to the new string
newPlainText += "x"
newPlainText += plaintext[i] # we insert the required current letter to the new string in each iteration
i += 1
print(newPlainText)

python string.split() and loops

disclaimer im new to python
i need to split a string input send if to a function that substitutes a character in the string with a different character (like a substitution cipher) but i just dont know how to go about this
print('Welcome to the encryption protocol for top secret governemt cover ups')
string=input('whats your message?')
def encrypt(string):
alpha = "abcdefghijklmnopqrstuvwyz"
sub_alpha = "pokmenliuytrwqazxcvsdfgbhn"
index=0
while index < len(string):
letter=string[index]
im not really sure what im doing im really bad at python, this has had me stumped for 3 days now ive reviewed my course material and tried videos on youtube im probably just really really dumb
I think the key piece of knowledge you're missing is that strings are iterable. So you can do things like:
for c in "FOO":
print(c)
# prints "F\nO\nO\n"
And you can find the index of a character within a string with str.index. So you can build up your cyphertext like this:
alpha = "abcdefghijklmnopqrstuvwyz "
cypher = "pokmenliuytrw qazxcvsdfgbhn"
plaintext = "some string"
cyphertext = ""
for c in plaintext:
char_index = alpha.index(c)
cyphertext += cypher[char_index]
You can also iterate over things inline - this is called a comprehension. So to transform your string you can do this instead of using the for loop:
cyphertext = "".join(cypher[alpha.index(c)] for c in plaintext)
The example above uses the str.join function to concatenate each character of cyphertext.
Here is a solution that asks the question and then iterates through each letter, finding the index in the alpha key, and replacing it with the sub_alpha key equivalent.
Note this example also checks if it should be lowercase or uppercase.
EDIT: if the input character does not have a valid cipher, it doesn't get altered.
EDIT 2: expanded answer to convert both forwards and backwards.
alpha = "abcdefghijklmnopqrstuvwyz"
sub_alpha = "pokmenliuytrwqazxcvsdfgbhn"
def encrypt(in_char):
is_lower_case = in_char.islower()
index = alpha.find(in_char.lower())
if index < 0:
return in_char
elif is_lower_case:
return sub_alpha[index]
else:
return sub_alpha[index].upper()
def decrypt(in_char):
is_lower_case = in_char.islower()
index = sub_alpha.find(in_char.lower())
if index < 0:
return in_char
elif is_lower_case:
return alpha[index]
else:
return alpha[index].upper()
print('Welcome to the encryption protocol for top secret governemt cover ups')
input_str=input('whats your message? ')
output_str=""
for letter in input_str:
output_str += encrypt(letter)
print("Encrypted: ")
print(output_str)
input_str=""
for letter in output_str:
input_str+= decrypt(letter)
print("Decrypted: ")
print(input_str)

Creating a Caeser program: Converting ASCII to characters

I'm working on python attempting to make a Caeser Cipher program.
So I've made a GUI platform and have been able to make the cipher part work, but it spits out the message only in ASCII.
When you run my program, it takes the info, you say the amount of letters you want the alphabet to shift by, and then says the message in ASCII, how can i get this part to come out in letters?
I tried storing the for loop into a variable then just adding that variable into a common ascii --> character converter, but that doesn't work.
Here's my code:
def encode(userPhrase):
msg = input('Enter your message: ')
key = eval(input("enter a number"))
finalmsg = msg.upper()
for ch in finalmsg:
print( str( ord(ch)+key ), end=' ')
Change your str to chr:
print( chr( ord(ch)+key ), end=' ')
Per the documentation on chr:
Return the string representing a character whose Unicode code point is the integer i. For example, chr(97) returns the string 'a', while chr(957) returns the string 'ν'. This is the inverse of ord().
You need to allow the letters at the end of the alphabet to wrap around to A,B,C... You can do it with modulo arithmetic (complicated), or see the example below
Use chr instead of str. You pass a parameter userPhrase and you ask to enter a message. Also, I suggest using int instead of eval.
def encode(userPhrase):
msg = input('Enter your message: ')
key = int(input("enter a number"))
finalmsg = msg.upper()
for ch in finalmsg:
new_ch = ord(ch)+key
if new_ch > ord('Z'):
new_ch -= 26
print( chr(new_ch), end=' ')
The last problem you have is for non letters (e.g. spaces, etc.)

How can I do this specific replacement

string = input("Enter a string:")
character = input ("Enter a character:")
if character in string: #checks string to see if character is in it
print ("Character found!")
else:
print ("Character not found!")
blank = (" _ " * len(string))
print (blank)
I am making a hangman game and I am stuck at this part.
How can I make it so that when the person guesses a letter it replaces the specific " _ " for where the letter should be? Should I be using a for loop to go through all the " _ " 's and then use an if statement?
If someone could show me how it would be great.
You can use a method similar to the following to update your guess string each time the player guesses something.
def update_guesses(current_guess_string, key_string, character_guessed):
out_string = ""
for i in range(len(current_guess_string)):
if current_guess_string[i] == "_" and key_string[i] == character_guessed):
out_string += character_guessed
else: out_string += current_guess_string[i]
return out_string
Building on your code, here's a set of changes that get you to a working game:
Set your string input to a fixed case and move your blank initialization to just after string input. Change them both to lists so you can modify
them as needed:
# normalize case and convert to a mutable data structure
string = list(input("Enter a string: ").lower())
# the blank list matches the string except it starts all blanks
blank = list("_" * len(string))
Get a loop going for handling guesses. Here I'm stopping the loop when the string list is all one character, presumably blanks, as we'll be swapping characters between the blank and input string lists:
while len(set(string)) > 1: # when the string is all blanks, stop
Make sure to cleanup the user input to be only one character and change it to the same case as the string input:
# normalize case on input and make sure to only get 1 character
character = input("Enter a character: ").lower()[0]
After you test if the character is in the string, swap the character, by position, with a character in the blank string. Do this in a loop so you get all instances of the guessed character:
if character in string: # checks string to see if character is in it
print("Character found!")
# find the character in the string and swap it with what's in
# the blank string so we can handle multiple same characters
# correctly
while character in string:
index = string.index(character)
string[index] = blank[index]
blank[index] = character
else:
print("Character not found!")
The last step in the main loop is to print the current state of the guesses:
# print correct guesses and blanks
print(*blank, sep='')

Repeating a word to match the length of a string [duplicate]

This question already has answers here:
Repeat string to certain length
(15 answers)
Closed 5 months ago.
In school we are currently using python to create a Caeser Cipher, and a Keyword Cipher. I need help with certain parts of the Keyword cipher, mainly repeating a word to match the length of a string that has been entered, For example:
Entered String: Hello I am Jacob Key: bye Printed text: byebyebyebyeb
I'm okay at Python, but this is very hard for me. Currently this is as far as I got:
def repeat_to_length(key, Input):
return (key * ((ord(Input)/len(key))+1))[:ord(Input)]
Since It's a string I thought if I used ord It would turn it to a number, but I realised when using the ord command you can only have a single character, as I realised when I repeatedly got this error:
TypeError: ord() expected a character, but string of length 16 found
I found some code that did a keyword cipher, but I am not sure which part does the process I am trying to code:
def createVigenereSquare():
start = ord('a')
end = start + 26
index = start
sq = [''] * 256
for i in range(start, end):
row = [''] * 256
for j in range(start, end):
if index > (end - 1):
index = start
row[j] = chr(index)
index += 1
sq[i] = row
index = i + 1
return sq
def createKey(message, keyword):
n = 0
key = ""
for i in range(0, len(message)):
if n >= len(keyword):
n = 0
key += keyword[n]
n += 1
return key
def createCipherText(message, key):
vsquare = createVigenereSquare()
cipher = ""
for i in range(0, len(key)):
cipher += vsquare[ord(key[i])][ord(message[i])]
return cipher
message = str(input("Please input a message using lowercase letters: "))
keyword = str(input("Please input a single word with lowercase letters: "))
key = createKey(message, keyword)
ciphertext = createCipherText(message, key)
print ("Message: " + message)
print ("Keyword: " + keyword)
print ("Key: " + key)
print ("Ciphertext: " + ciphertext)
As I've said I'm only okay at Python, so I don't really understand all the code in the above code, and I really want to be able to write most of it myself.
This is my code so far:
def getMode():
while True:
print("Enter encrypt, e, decrypt or d")
mode = input('Do you want to encrypt the message or decrypt? ') #changes whether the code encrypts or decrypts message
if mode in 'encrypt e decrypt d ENCRYPT E DECRYPT D'.split(): #defines the various inputs that are valid
return mode
else:
print('Enter either "encrypt" or "e" or "decrypt" or "d".') #restarts this string of code if the input the user enters is invalid
Input = input('Enter your message: ')
key = input('Enter the one word key: ')
def repeat_to_length(key, Input):
return (key * ((ord(Input)/len(key))+1))[:ord(Input)]
encryptedKey = repeat_to_length(key, Input)
print(encryptedKey)
I know I've been pretty long winded but if anyone could provide any information on this topic, like explaining the code for the keyword cipher, or just answering my question, I would appreciate it!
AnimeDeamon
Another possibility is to repeat the key enough times to get a string at least as long as the message, and then just grab as many characters as you need from the beginning:
>>> message='Hello I am Jacob'
>>> key='bye'
>>> times=len(message)//len(key)+1
>>> print((times*key)[:len(message)])
byebyebyebyebyeb
We compute times by dividing the length of the message by the length of the string, but we have to add 1, because any remainder will be dropped. times*key is just key repeated times times. This may be longer than we want, so we just take the first len(message) characters.
A simple one-liner could be
''.join(key[i % len(key)] for i in range(len(message)))
What it does, inside out:
We iterate over the indices of the letters in the string message
For each index, we take the remainder after division with the key length (using the % operator) and get the corresponding letter from the key
A list of these letters is constructed (using list comprehension) and joined together to form a string (the join method of an empty string)
Example:
>>> message = "Hello I am Jacob"
>>> key = "bye"
>>> print ''.join(key[i % len(key)] for i in range(len(message)))
byebyebyebyebyeb

Categories

Resources