I don't understand this KeyError? - python

I'm doing this challenge where i am tasked at coding up a game of hangman - where I am supposed to reduce the range of words in a set.The rules of the game states that you get 8 tries too guess otherwise you'd lose.If the user were to key in the same letter more than once a message would pop up stating that he's already done so - I've used sets as a way to handle this part of the game. Below is my code:
word_list = ["python", "java", "kotlin", "javascript"]
word = random.choice(word_list)
word_set = set(word)
hidden = []
for i in word:
hidden.append("-")
# print(hidden)
print("H A N G M A N")
count = 0
while(count < 8):
print()
print("".join(hidden))
guess = input("Input a letter: ")
if guess in word:
if guess not in word_set:
print("No improvements")
count += 1
else:
for i in range(len(word)):
if word[i] == guess:
print(word_set)
word_set.remove(word[i])
hidden[i] = word[i]
if word_set == set():
print()
print(word)
print("You guessed the word!")
print("You survived!")
else:
print("No such letter in the word")
count += 1
print("You are hanged!")
The main problem I face is an error telling me that 'a' and only 'a' in particular is a key error which goes like this: Traceback (most recent call last):
File "/Users/laipinhoong/Desktop/learnpython.py/learning.py", line 29, in <module>
word_set.remove(word[i])
KeyError: 'a'

The problem appears when the chosen word has the same letter more once. In that case, since you iterate over all the letters in word (for i in range(len(word))) you will try to remove this word few times from the set word_set (as much as this letter appears in the word) but word_set will have this letter only once since set is unique collection. So in the second attempt to delete a from javascript or java, word_set.remove(word[i]) will fail cause the set will not contain this letter anymore.
In order to prevent the error, try to use:
word_set.discard(word[i]) instead. In that case, the letter will be removed if exists and if not, no exception will be raised.

You try to remove the same letter multiple times because you iterate the word - iterate its set of letters instead. You could also precalculate the positions of each letter in your word into a dictionary and use that to "fill in the gaps" like so:
word = "javascript"
seen = set() # letters that were guessed get added here
letters = set(word) # these are the letters to be guessed
hidden = ["_" for _ in word] # the output
positions = {l:[] for l in letters } # a dictionary letter => positions list
for idx,l in enumerate(word): # add positions of each letter into the list
positions[l].append(idx)
print("H A N G M A N")
count = 0
while count < 8:
print()
print("".join(hidden))
# allow only 1-letter guesses
guess = input("Input a letter: ").strip()[0]
# if in seen it is a repeat, skip over the remainder of the code
if guess in seen:
print("Tried that one already.")
continue
# found a letter inside your word
if guess in positions:
# update the output list to contain this letter
for pos in positions.get(guess):
hidden[pos]=guess
# remove the letter from the positions list
del positions[guess]
else: # wrong guess
count += 1
print("No improvements: ", 8-count, "guesses left.")
# remember the seen letter
seen.add(guess)
# if the positions dictionary got cleared, we have won and found all letters
if not positions:
print(word)
print("You guessed the word!")
print("You survived!")
break
# else we are dead
if count==8:
print("You are hanged!")
Output:
__________
Input a letter:
j_________
Input a letter:
ja_a______
Input a letter:
java______
Input a letter:
javas_____
Input a letter:
javasc____
Input a letter:
javascr___
Input a letter:
javascri__
Input a letter:
javascrip_
# on bad inputs:
No improvements: 7 guesses left.
# on win
javascript
You guessed the word!
You survived!
# on loose
You are hanged!

Your key error is going to happen every time you pick a letter that is repeated in the word. When you do word_set.remove(word[i]) inside a for i in range(len(word)): loop and the word has the same letter at multiple is, this key error will occur when it hits the second i corresponding to that letter in the word. This will make more sense to you if you step through your code in python tutor.

You need to understand what your code does:
When you remove a character from word_set.remove(word[i]). This removes it but on 2nd iteration it doesn't find the character thus it throws the key error because it cannot find the key which is already removed.
Try adding an if condition like in this code to check if key exists before removal practically bypass if it doesnt exists and save you from keyerror
import random
word_list = ["python", "java", "kotlin", "javascript"]
word = random.choice(word_list)
print(word)
word_set = set(word)
hidden = []
for i in word:
hidden.append("-")
#print(hidden)
print("H A N G M A N")
count = 0
while(count < 8):
print()
print("".join(hidden))
guess = input("Input a letter: ")
if guess in word:
if guess not in word_set:
print("No improvements")
count += 1
else:
for i in range(len(word)):
if word[i] == guess:
if word in word_set:
word_set.remove(word[i])
hidden[i] = word[i]
if word_set == set(hidden):
print()
print(word)
print("You guessed the word!")
print("You survived!")
else:
print("No such letter in the word")
count += 1
print("You are hanged!")

Set.remove() throws a KeyError if the item you are removing is not part of the set.
In your case, it's caused by the word_set and word not having the same letters.
E.g. If word = java, then word_set = ( j, a, v)
And since you are looping over word instead of word_set, your code will attempt to remove the letter 'a' twice from word_set, which will result in a keyError

Related

Issue with analyzing strings in Python for Wordle-esque progam

I've created a function that takes a user-inputted guess, compared it to a hidden word taken randomly from a word doc, and returns a string that indicates if any letters match or are in the word at all. Here is the function:
def wordResults(guess, testGuess):
#guess = user inputted guess
#testGuess = secret word
results = ""
for i in range(5):
#Check if letters at given position match
#in each word, append capital letter if so
if guess[i] == testGuess[i]:
results += guess[i].upper()
#Check if letter at given position is in
#the secret word at all, append lowercase
#letter if so
elif testGuess.find(guess[i]) != -1:
results += guess[i]
#Append underscore if neither condition is met
else:
results += "_"
return results
My issue lies with the elif-statement. I would like it to print a lowercase only if that letter appears in the word, but not if the letter is already in the correct spot. Here is the program running to show what I'm referring to:
(Note: the hidden word is also user-inputted until I get the program working as intended)
For Guess #2, I would like it so that the first 'h' does not show up, since it is indicating the 5th letter in 'conch' that is already confirmed with a capital 'H'. Hope that makes sense.
It's a lot easier to work with a list and then make it a string at the end:
guess = guess.lower()
testGuess = testGuess.lower()
result = []
for i, letter in enumerate(guess):
if letter in testGuess:
if letter == testGuess[i]:
result.append(letter)
else:
result.append(letter.upper())
else:
result.append('_')
for i, letter in enumerate(result):
if letter.upper() in result and result[i] != letter:
result[i] = '_'
return ''.join(result)
For guess two, then the second loop checks if each letter is already in the loop and placed correctly and if it's not in the correct spot, makes it back into an _.

Looping over characters (and their indices) in Python [duplicate]

So I'm making a hanging man game and I have run into a problem regarding indexes. Basically, I want to find the index of a letter inside a secret word, the problem is that if the secret word includes two letters that are the same, for instance, "guacamole", where the letter a has the index of 2 and 4 but when I want to find the index of a, it only prints "2" and not "4". Is there a way around this? Thanks in advance!
Part of code where problem occurs:
for letter in secret_word:
if user_guess == letter:
current_word_index = secret_word.find(letter)
print(current_word_index) #Not in full program, only to test errors.
Full code:
#Hanging man
import string
space = "\v"
dbl_space = "\n"
secret_word = str(input("Enter a secret word: "))
guess_low = list(string.ascii_lowercase)
used_letters = []
user_errors = 0
user_errors_max = 1
secret_word_index = int(len(secret_word))
secret_word_placeholder = list(range(secret_word_index))
while user_errors != user_errors_max:
user_guess = str(input("Enter a letter: "))
if len(user_guess) != 1:
print("You have to pick one letter")
if user_guess in guess_low:
guess_low.remove(user_guess)
used_letters.extend(user_guess)
print(used_letters)
for letter in secret_word:
if user_guess == letter:
current_word_index = secret_word.find(letter)
if user_errors == user_errors_max:
print("You lost the game, the secret word was: " + secret_word)
This is an example of what you are trying to achieve. use list comprehension.
string='hello'
letter='l'
[idx for idx,ch in enumerate(string) if ch==letter]
Python's string find accepts a start parameter that tells it where to start searching:
>>> "guacamole".find('a')
2
>>> "guacamole".find('a', 3)
4
Use a loop, and use the index of the last hit you found + 1 as the start parameter for the next call.
Another more verbose solution might be:
str1 = "ooottat"
def find_all_indices(text, letter):
indices_of_letter = []
for i, ch in enumerate(text):
if ch == letter:
indices_of_letter.append(i)
return indices_of_letter
print(find_all_indices(str1, 'o'))
Side note:
Indexes is the nontechnical plural of index. the right technical plural for index is indices
Yes, if you instantiate a new_variable to be the secret_word variable before the for loop, the in the line current_word_index = secret_word.find(letter) change secret_word.find(letter) to new_variable .find(letter) then below the if statement write new_variable = new_variable [1:] which will remove the letter just found.
so your code would look something like:
new_variable = secret_word
for i in secret_word
if user_guess == letter:
current_word_index = new_variable.find(letter)
#I would do print(current_word_index) this will show the index
#Or current_word_index = current_word_index + ',' + new_variable.find(letter)
new_variable= new_variable[1:] #this will take away your letter.

How to replace the specified dash with the letter

I wish to write a hangman program and in order to do so, I have to replace the hash ('-') letter(s) with the user's guessed letter (guess). But when I run the code, it replaces all the hashes with the user's guess letter.
The code seems okay but I don't get the desired result.
words is a list of words I have written before the function.
def word_guess():
random.shuffle(words)
word = words[0]
words.pop(0)
print(word)
l_count = 0
for letter in word:
l_count += 1
# the hidden words are shown a '-'
blank = '-' * l_count
print(blank)
guess = input("please guess a letter ")
if guess in word:
# a list of the position of all the specified letters in the word
a = [i for i, letter in enumerate(word) if letter == guess]
for num in a:
blank_reformed = blank.replace(blank[num], guess)
print(blank_reformed)
word_guess()
e.g: when the word is 'funny', and guess is 'n', the output is 'nnnnn'.
How should I replace the desired hash string with guess letter?
it replaces all the hashes
This is exactly what blank.replace is supposed to do, though.
What you should do is replace that single character of the string. Since strings are immutable, you can't really do this. However, lists of strings are mutable, so you could do blank = ['-'] * l_count, which would be a list of dashes, and then modify blank[num]:
for num in a:
blank[num] = guess
print(blank)
A couple things to note:
inefficient/un-pythonic pop operation (see this)
l_count is just len(word)
un-pythonic, unreadable replacement
Instead, here's a better implementation:
def word_guess() -> str:
random.shuffle(words)
word = words.pop()
guess = input()
out = ''
for char in word:
if char == guess:
out.append(char)
else:
out.append('-')
return out
If you don't plan to use the locations of the correct guess later on, then you can simplify the last section of code:
word = 'hangman'
blank = '-------'
guess = 'a'
if guess in word:
blank_reformed = ''.join(guess if word[i] == guess else blank[i] for i in range(len(word)))
blank_reformed
'-a---a-'
(You still have some work to do make the overall game work...)

how to remove letter from one list to the other randomly but to the right location

I just started to learn python and as part of my learning i am trying to make simple programs.
so i tried to make an hangman game and i am stuck.
i cant think of a better way to identify the letter in the word the player need to guess - - and adding the letter at the right place in var of what the player guess so far.
The code:
import random
list_of_words = ["door", "house", "work", "trip", "plane", "sky", "line", "song", "ever","life"]
random_word = list_of_words[random.randint(0, len(list_of_words) - 1)]
guessed_word = "_" * len(random_word)
print("Hello!, welcom to my hangman Game")
print("try to guess what word could that be??? ")
print(guessed_word)
guessed_word = guessed_word.split()
random_word = list(random_word)
print(random_word, guessed_word)
while len(random_word) != 0:
letter = input("guess a letter >>>... ")
if letter in random_word:
print(f"YAY! thats right, {letter} is in the word")
index = random_word.index(letter)
random_word.remove(letter)
guessed_word[index] = letter
print("this is your word now >> ", guessed_word)
else:
print("the word isnt there")
print("yay, you found the word")
So, the code is running good and it identify when the player finished choosing all the right letters. the problem is how to copy the word and show players progress. so at the start it puts the letter in the right place, but after that it doesn't change the letter location.
Couple things wrong here.
finding only one occurrence of the letter (door would only find the first o)
Removing items loses track of the index
guessed_word splitted wrong
These issues fixed:
import random
list_of_words = ["door", "house", "work", "trip", "plane", "sky", "line", "song", "ever","life"]
random_word = list_of_words[random.randint(0, len(list_of_words) - 1)]
guessed_word = "_" * len(random_word)
print("Hello!, welcom to my hangman Game")
print("try to guess what word could that be??? ")
# Convert to list first
guessed_word = list(guessed_word)
# Print separated with spaces second
print(' '.join(guessed_word))
random_word = list(random_word)
# Check if the guessed word is the same as the random word, if it is, the word is fully guessed
while random_word != guessed_word:
letter = input("guess a letter >>>... ")
if letter in random_word:
print(f"YAY! thats right, {letter} is in the word")
# find all indices of the letter
indices = [i for i, x in enumerate(random_word) if x == letter] # * see notes
# Update all indices of the guessed word with the letter
for i in indices:
guessed_word[i] = letter
# Don't remove the letter from the random word
# Print with spaces, because it's nicer that way!
print("this is your word now >> ", ' '.join(guessed_word))
else:
print("the word isnt there")
print("yay, you found the word")
Hope this helped! Have fun using python!
Notes:
List comprehension can be scary, have a look at this
You just need to replace
guessed_word = guessed_word.split()
with:
guessed_word = list(guessed_word)
and the reason why on example:
>>> x="work"
>>> x.split()
['work']
>>> list(x)
['w', 'o', 'r', 'k']
>>>
x.split() splits sentence per white space as a delimiter - you want to split it letter by letter instead.

How do i find out how many individual letters there are in a word in python?

I'm using pyscripter to create a hangman game. I have managed to get everything to work except one thing. This is that once i have found the correct word the script needs to match this to the secret word. This would usually be easy but the way i have done it leaves gaps in the string.
What i want to do is using the number of letters in the secret word; when i enter a letter it looks for that letter and adds how many times the letter appears in the secret word. i.e. the letter "P" in APPLE appears 2 times, therefore adding 2 to a separate string. if the word was "APPLE" the program would be looking for 5 correct letters.
This way i can make an "if" statement and end the game once the numbers of correct letters guessed matches the length of the secret word.
This is the program i am using : http://i1.ytimg.com/vi/1HZ38RzykuE/maxresdefault.jpg
Does this make sense, I've been pondering on it for a while so it may be jumbled.
Thank you if your able to help.
This is the code i am using:
(blanktotal is the length of the secret word)
else:
if letter in secretword:
letterscorrect = letterscorrect + 1
os.system("cls")
if letter not in guessedletters:
os.system("cls")
for x in range(0, len(secretword)):
if letter == secretword[x]:
for x in range(len(secretword)):
if secretword[x] in letter:
hiddenletter = hiddenletter[:x] + secretword[x] + hiddenletter[x+1:]
guessedletters.append(letter)
else:
print("")
else:
print("")
for letter in hiddenletter:
print(letter, end=' ')
print("")
if letterscorrect == blanktotal:
os.system("cls")
print("")
print("congratulations you have won!!!!")
print("You are now the master of HANGMAN!!!!!")
print("")
You can use the count() method to count the number of occurrence of a letter.
secret = "Apple"
secret.count('p')
gives
2
I think you're just looking for the count method:
>>> s = 'APPLE'
>>> s.count('P')
2

Categories

Resources