1
\$\begingroup\$

I am making a simple function to find and match any possibilities what Pokémon the user wants. This is accomplished by taking in the input (what the user thinks the Pokémon is named) and compare its spelling with preset Pokémon names (letter by letter searching). With each successful match, the Pokémon's score (determined by its value in the dictionary) will go up by one. Only the top seven Pokémon are returned.

The function:

import re
import operator

# Dictionary of Pokemon
list_of_chars = {"Bulbasaur": 0, "Ivysaur": 0, "Venusaur": 0, "Squirtle": 0, "Wartotle": 0, "Blastoise": 0,
                 "Charmander": 0, "Charmeleon": 0, "Charizard": 0, "Zapdos": 0, "Articuno": 0, "Moltres": 0, "Mew": 0,
                 "Mewtwo": 0}
list_of_keys = []

# Allow the list to actually contain the keys
for key in list_of_chars:
    list_of_keys.append(str(key))


def indiv_search(entry):
    certain_list = []
    # Loop through the possible Pokemon
    for i in range(0, len(list_of_keys)):
        # Loop through each *letter* of the entry
        for b in range(0, len(entry)):
            try:
                # Match each letter of entry with the letter of the Pokemon
                m = re.search(str(entry[b]), list_of_keys[i][b], re.IGNORECASE)
            except IndexError:
                continue
            if m is not None:
                # Match is successful 
                list_of_chars[list_of_keys[i]] += 1
            elif m is None:
                # Match is not successful
                continue
    # "Sort" dictionary by score values
    sorted_x = sorted(list_of_chars.items(), key=operator.itemgetter(1))
    # Take only top seven choices
    while len(sorted_x) > 7:
        del sorted_x[0]
    for i in range(6, -1, -1):
        certain_list.append(sorted_x[i][0])
    # Return results
    return "Possible Choices: " + ", ".join(certain_list)

while True:
    # Take input, keep in string format
    poke_name = str(raw_input("Pokemon's Name: "))
    if poke_name == "QUIT":
        break
    else:
        # Print results
        print indiv_search(poke_name)
        # Reset all values in dictionary
        for i in range(0, len(list_of_chars)):
            list_of_chars[list_of_keys[i]] = 0

Example output:

Pokemon's Name: Char  
Possible Choices: Charmander, Charizard, Charmeleon, Blastoise, Articuno, Venusaur, Zapdos

Note that the first line contains the raw_input() prompt and the input (separated by a space due to the prompt purposely having a space at the end to improve readability). The second line has the results of the function.

I have a few questions:

  • Is the code Pythonic?

  • What can be improved to uphold PEP rules?

  • What can be done to improve the efficiency and if possible, accuracy of the code?

\$\endgroup\$
8
  • \$\begingroup\$ Have you seen difflib? \$\endgroup\$
    – zondo
    Commented Aug 5, 2016 at 18:00
  • \$\begingroup\$ @zondo Interesting. It looks cool but maybe a bit hard to implement for now. The get_close_matches() might be useful though \$\endgroup\$ Commented Aug 5, 2016 at 18:01
  • \$\begingroup\$ Am I misunderstanding, or are you using re.search where you could use in? \$\endgroup\$
    – zondo
    Commented Aug 5, 2016 at 18:09
  • \$\begingroup\$ What do you mean by in? Like a in b? \$\endgroup\$ Commented Aug 5, 2016 at 18:12
  • \$\begingroup\$ Yes. Why use re.search(x, y, re.IGNORE_CASE) instead of x.lower() in y.lower()? \$\endgroup\$
    – zondo
    Commented Aug 5, 2016 at 18:15

1 Answer 1

2
\$\begingroup\$

There are much simpler ways to do this. The easy way is to use difflib.get_close_matches() which is very similar to what you are creating. If you insist on making your own, there are still simpler ways.


First of all, why regex? You don't have patterns to match. You have simple equality tests. The only thing that makes it different from a in b is re. IGNORE_CASE. Simply fixed, do a.lower() in b.lower(). In this case, however, you actually mean a.lower() == b.lower().


Iterating through two sequences in parallel is a common enough task that Python made it easy with zip(). Instead of

for i in range(len(a))):

with a[i] and b[i], you can use

for a_item, b_item in zip(a, b):

Conveniently, it stops at the end of the shortest iterable (not in Python 3).


Your method of limiting the number of matches returned is not ideal. Instead of deleting the ones you don't want, keep only the ones that you do. That is done simply with a slice.


In Python, the bool class (for True and False) inherits from the int class. Therefore, you can do arithmetic with booleans. True is 1, and False is 0. With that knowledge and the earlier improvements, we get something much simpler:

def diff_score(iter_a, iter_b):
    return sum(a == b for a, b in zip(iter_a.lower(), iter_b.lower()))

def best_matches(guess, names, num_matches):
    matches = sorted(names, key=lambda x: diff_score(x, guess), reverse=True)
    return  matches[:num_matches]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.