I don't understand this KeyError?

Viewed 649

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'

5 Answers

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