Chris Schell Chris Schell - 3 months ago 7
Python Question

Returning False to Break out of a loop

I am trying to break out of this loop. I have returned False and added a break if the character in the word is not on the hand, which is a dictionary with integer values per letter, based on Scrabble letter scores. This is part of a larger game but this particular function checks to see if the word I've entered is valid.

Now the issue I'm having is if the word entered is not in the word list the input is rejected and the player is asked to enter another word. HOWEVER...if I enter a single letter for instance, the hand is still updated to have that letter removed, and is therefore not available when I retype a word using all three letters.

For example:

if my hand is r u t

and I enter u, my word will be invalid, but the hand now contains only r t. Therefore rut will no longer be available.

I have a pretty good grasp of the loops and return statements after the last 24 hours straight of coding, except I can't figure out how to structure this loop to avoid that issue.

Here is the code:

def is_valid_word(word, hand, word_list):
"""
Returns True if word is in the word_list and is entirely
composed of letters in the hand. Otherwise, returns False.
Does not mutate hand or word_list.

word: string
hand: dictionary (string -> int)
word_list: list of lowercase strings
"""

remaining = hand
for c in word:
if c not in remaining.keys(): """Loop 1"""
return False
break
if c in remaining.keys(): """Loop 2"""
remaining [c] -= 1
if remaining [c] < 0: """Loop 3"""
return False
if word in word_list:
return True
else:
return False


However I structure the loops eventually a condition will fail. If I indent Loop 2 and 3 then they'll fail if the letter is not in the hand, and vice versa. Obviously a simple fix would be something like break(n loops) but that doesn't exist in Python. Any suggestions to make this happen? I've been working on it a long time as I'm new to programming.

Thanks in advance!

Answer

There are several problems in your code, I have refactored it to make it clearer (and fixed it, that was the point :))

minor issues:

  • not optimal: first check if word is in word_list (you'd better use a set rather than a list, would be much faster), then test for available letters
  • several return statements, followed by break. Confusing.

major issue:

The main problem being that you affect remaining to hand and you change it, thus destroying your head each time you call the function which explains the behaviour you're experiencing.

You have to copy the dictionary, and it will work. Python parameters are passed by reference, so if you pass a list or a dict and you modify it within the function, then it remains modified, unless you make a copy of it.

No real problems with the break & returns: my idea is that you think it does not work when you call your function several times in a row, when first call just destroys your input data.

def is_valid_word(word, hand, word_list):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.

    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    # make a copy or hand is destroyed by your test
    remaining = hand.copy()  
    rval = False

    if word in word_list:
        rval = True
        # don't check if word is not in word_list (optim)
        for c in word:
            if c not in remaining: # no need to specify keys()
                rval = False
                break

            remaining [c] -= 1
            if remaining [c] < 0:
                rval = False
                break

    return rval
Comments