Issue when checking for special character in a string

When running through this checker loop and using the wild_card ‘*’ as the first character in the for loop below it returns false even when the word should be valid. Otherwise, the loop functions as intended.

Does anyone have any ideas as to why this would be? I have the first check commented out for now while troubleshooting.

Example: word = ‘*g’
wrd in word_list = ‘ag’
Invalid word

word = ‘al*’
wrd in word_list = 'ale
Valid word

def is_valid_word(word, hand, word_list):
    print(hand)
    print(len(hand))
    print(len(word))
    print(list(word))
    #for l in word: 
        #if l not in hand:
            #return False
    for wrd in word_list:  # so go through all possible words
        if len(wrd) == len(word):
            for char, my_char in zip(word, wrd):
                if my_char != char and my_char != '*':
                    break
                else:
                    return True

Here’s the function call if needed.

while num_hands >= 0:
        word = input("Enter a word from your hand: ")
        if word == '!!':
            break
        else:
            get_frequency_dict(word)
            if is_valid_word(word, hand, word_list) != True:
                print("This is not a valid word.")
                word = input("Please enter another word: ")
                type(word)
                type(hand)
                new_hand = update_hand(hand, word)
                new_hand_copy_n = new_hand.copy()
            else:
                print(get_word_score(word, n))
                running_score += get_word_score(word, n)
                new_hand = update_hand(hand, word)
                new_hand_copy_n = new_hand.copy()

You only check if my_char != '*' (wrd) but not if char != '*' (word). But in your examples the * is in word, not in wrd.

You only ever check the first character in a word (and then always either return True or break). So the second and later characters in the words don’t matter.

Consider writing a function to check just against one single word, instead of a list of words. Then write a second function for the list of words, and use the first function. That remove some confusion about what loop you are exiting when and why.

Okay thank you. I’m trying to do something like this code listed below. Except instead of checking all words in wordlist for inclusion and adding them to a list if they match my criteria. I only need to find one word that fits then end/break/finish the loop.

    for word in wordlist:  # so go through all possible words
        if len(word) == len(my_word):
            for my_char, char in zip(my_word, word):
                if my_char != char and my_char != '_':
                    break
            else:
                other_word.append(word)

Thank you for your advice!

This worked.

   for l in word:
        if l not in hand:
            return False
    for wrd in word_list:  # so go through all possible words
        if len(wrd) == len(word):
            for my_char, char in zip(word, wrd):
                if my_char != char and my_char != '*':
                    break
            else:
                return True

Also, if anyone has any comments on a cleaner or better way of writing this code I would interested to know.

Something using str.find(’*’) and regex combined or something like that. I’ll mess around with it but I am mostly unfamiliar with both.

By Rufus Polk via Discussions on Python.org at 19Apr2022 16:23:

This worked.

[…]

       for my_char, char in zip(word, wrd):
           if my_char != char and my_char != '*':
               break
       else:
           return True

Notice the distinction here:

Your original code was an else: for the if-statement. That would
return True as soon as the if-statement test failed.

Your new code uses the uncommon (but really good for certain things)
else: for the for-loop, which fires if the loop is not exited with a
break. Specification here:

So if the if-statement test succeeds, we break from the loop and fall
off the end of the function, returning None (which is a false value).

If the if-statement test never succeeds, we exit the loop normally and
run the else: suite, returning True.

BTW, I like functions which return a value to explicitly return a value
at all return points including the bottom. I’d end the function with an
explicit return False. Various lint tools will warn about this, as
will type checkers if you annotate the function as returning a bool.

And put some comments in, particularly above fiddly tests like that
if-statement. Otherwise someone reading the code needs to “figure out”
what the effect of the test is, instead of being told the intended
purpose; this makes the code hard to understand, and sufficiently later
“somemone” will be you! And meanwhile, “someone” is us, whom you have
asked for help.

Cheers,
Cameron Simpson cs@cskk.id.au

Okay thank you very much. I am definitely not great at adding comments on my code. I will work on that for sure. Also, thank you for the documentation and a better understanding of the for loop used here.

Also, I know this probably should be pretty simple but how would I return False on this like you are suggesting since I am using an initial break statement at the bottom of the function?

By Rufus Polk via Discussions on Python.org at 20Apr2022 14:48:

Also, I know this probably should be pretty simple but how would I
return False on this like you are suggesting since I am using an
initial break statement at the bottom of the function?

You had:

for l in word:
    if l not in hand:
        return False
for wrd in word_list:  # so go through all possible words
    if len(wrd) == len(word):
        for my_char, char in zip(word, wrd):
            if my_char != char and my_char != '*':
                break
        else:
            return True

If you break from the loop you land at the bottom of the function,
which implicitly returns None. That is a false value, but it is not
False. I’d do this:

for l in word:
    if l not in hand:
        return False
for wrd in word_list:  # so go through all possible words
    if len(wrd) == len(word):
        for my_char, char in zip(word, wrd):
            if my_char != char and my_char != '*':
                break
        else:
            return True
return False

Remembering that break just exits the loop. You leave the function by
then falling off the end.

By having a final return False it is clear that the whole function
always returns False unless specific earlier conditions are met.

Cheers,
Cameron Simpson cs@cskk.id.au

Thank you. I am beginning to have a slightly better understanding of how it goes through the steps of the function.

The function ended up being a little more complex than I originally had written simply due to some overlooked checks needed to meet the criteria for a valid word or return True

   word = word.lower()
    position = word.find('*')
    word = get_frequency_dict(word)
    
    for [key, val] in word.items():
        if key not in hand: #checking if word is composed of letters from hand
            return False
        if word[key] > hand[key]:
            return False  #checking if hand has enough of those letters to make the word
    for wrd in word_list: 
        if len(wrd) == len(word):  #eliminating possibilities based off length of word
            for my_char, char in zip(word, wrd): #running through characters side by side
                if my_char != char and my_char != '*': #checking for match or character exception
                    break
            else:
                if '*' in word: #checking for presence of character, wildcard character is only
                    for letter in wrd: #usable in place of a VOWEL character
                        if letter in VOWELS:
                            vowel_pos = wrd.find(letter)
                            if vowel_pos == position:
                                return True
                else:
                    return True #No Wildcard found
    return False #no matching words