4
\$\begingroup\$

I'm not really seeing any use for a generator here, unless there's some way to hook a generator into a language dictionary.

But in the following code, the longhand version of the if statement (type_check()) returns a tuple (foo, bar) but to get the tuple from (my first ever) lc (type_identify) it needs to be called from result[0], as opposed to simply result.

Is this normal or am I doing something wrong in the lc?

word_types = {'noun': ['bear', 'princess', 'door'], 
            'verb': ['go', 'run', 'hide', 'stop', 'close', 'kill', 'eat'], 
            'stop': ['in', 'of', 'at', 'to', 'from', 'the'], 
            'direction': ['up', 'down', 'north', 'south', 
                        'east', 'west', 'back', 'forward']}

def type_check(word):
    word = word.lower()
    for label, type in word_types.items():
            if word in type:
                return (label, word)

def type_identify(word):
    word = word.lower()
    return [ (label, word) for label, type in word_types.items() if word in type ]

def scan(sentence):
    sentence_list = sentence.split()
    for k, v in enumerate(sentence_list):
        if type_identify(v):
            sentence_list[k] = type_identify(v)[0] #needed to add [0]
        else:
            try:
                sentence_list[k] = ('number', int(v))
            except ValueError:
                sentence_list[k] = ('error', v)
    return sentence_list
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

That's normal, because with the list comprehension you're generating a whole list of results instead of returning the first time the check succeeds.

If you want to have the equivalent code to the loop version, that is, just compute the first value or return None if no check succeeds, you can use a generator as follows:

return next(((label, word)
             for label, type in word_types.items()
             if word in type),
            None)

where the generator is exactly as the list comprehension, but with parentheses instead of square brackets and next is a built-in method to get the next value from an iterator or return the default value if the iterator is exhausted.

Note: if you don't pass a default value you'll get a StopIteration when the iterator is exhausted.

\$\endgroup\$
2
  • \$\begingroup\$ Oh yea, I've seen these short-hand generators before. Thanks so much for the clear explanation. Kind of more readable in multiple lines, too. But would the next call to the generator start at the beginning again because we're actually making a new "instance" of it? \$\endgroup\$
    – MikeiLL
    Commented Jul 25, 2014 at 19:01
  • 1
    \$\begingroup\$ Yes, in the next call to the function a new generator will be created and just the first element will be calculated. \$\endgroup\$
    – jcollado
    Commented Jul 25, 2014 at 22:39
1
\$\begingroup\$

It seems to me you could greatly benefit from storing your dictionary reversed. That is, if you create, just once, an auxiliary dictionary as:

type_words = {word : label for label, words in word_types.items() for word in words}

Then all of a sudden your checks turn into a simple dictionary look-up, which is constant time, instead of having to scan all items in your dict every time:

label = type_words.get(word, None)
return (label, word) if label is not None else None

You are actually scanning the whole dict twice for every word (two calls to type_identify), so I would probably refactor even further, get rid of the type_identify and have the following single one:

def scan(sentence):
    sentence_list = sentence.split()
    for idx, word in enumerate(sentence_list):
        word_type = type_words.get(word, None)
        if word_type is not None:
            sentence_list[idx] = (word_type, word)
        else:
            try:
                sentence_list[idx] = ('number', int(word))
            except ValueError:
                sentence_list[idx] = ('error', word)
    return sentence_list
\$\endgroup\$
2
  • \$\begingroup\$ very awesome. so the dictionary lookup returns the virtual equivalent of {bear: noun, princess: noun, etc...}, right? Your final code is working, but when I tried to run if type_identify(v) is not None, return (label, word) if label is not None else None was returning [None] and I couldn't figure out how to make is not None true. \$\endgroup\$
    – MikeiLL
    Commented Jul 25, 2014 at 23:05
  • \$\begingroup\$ Ran into a complication trying to make the dictionary insensitive. Found a class that customizes __getitem__ but not sure how to make the above generator dictionary an instance of that. working around it by moving adding a try before the comparisons to dictionary and assigning word to lc_word, then excepting an AssertionError and flowing into the number and error clauses. \$\endgroup\$
    – MikeiLL
    Commented Jul 26, 2014 at 0:10

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.