2

I am working on the exercise 48 of Learn Python The Hard Way and writing the lexicon dictionary and the scan module to run the following test on:

from nose.tools import *
from ex47 import lexicon


def test_directions():
    assert_equal(lexicon.scan("north"), [('direction', 'north')])
    result = lexicon.scan("north south east")
    assert_equal(result, [('direction', 'north'),
                          ('direction', 'south'),
                          ('direction', 'east')])

def test_verbs():
    assert_equal(lexicon.scan("go"), [('verb', 'go')])
    result = lexicon.scan("go kill eat")
    assert_equal(result, [('verb', 'go'),
                          ('verb', 'kill'),
                          ('verb', 'eat')])


def test_stops():
    assert_equal(lexicon.scan("the"), [('stop', 'the')])
    result = lexicon.scan("the in of")
    assert_equal(result, [('stop', 'the'),
                          ('stop', 'in'),
                          ('stop', 'of')])


def test_nouns():
    assert_equal(lexicon.scan("bear"), [('noun', 'bear')])
    result = lexicon.scan("bear princess")
    assert_equal(result, [('noun', 'bear'),
                          ('noun', 'princess')])

def test_numbers():
    assert_equal(lexicon.scan("1234"), [('number', 1234)])
    result = lexicon.scan("3 91234")
    assert_equal(result, [('number', 3),
                          ('number', 91234)])


def test_errors():
    assert_equal(lexicon.scan("ASDFADFASDF"), [('error', 'ASDFADFASDF')])
    result = lexicon.scan("bear IAS princess")
    assert_equal(result, [('noun', 'bear'),
                          ('error', 'IAS'),
                          ('noun', 'princess')])

I have most of this figured out, except the numbers and errors tests. When the user input is a word not defined in the dictionary, it needs to be tagged with the error value name, and if it's a number - the number value name. Obviously, I can preemptively add all the inputs that will be tested into the dictionary, but that's cheating.

my dictionary looks like this:

lexicon = {
    'north': 'direction', 
    'princess': 'noun',
    # etc...
}

Is there a way to work, uh, "open-ended" definitions for numbers and undefined words into this?

UPDATE. Here is a working solution :

def scan(sentence):
    words = sentence.split()
    pairs = []

    for word in words:
        try:
            number = int(word)
            tupes = ('number', number) 
            pairs.append(tupes)

        except ValueError: 
            try:
                word_type = lexicon[word]
                tupes = (word_type, word) 
                pairs.append(tupes)
            except KeyError:
                tupes = ('error', word)
                pairs.append(tupes)
    return pairs
sivanes
  • 713
  • 1
  • 13
  • 22
  • Your solution looks good. I might suggest a way to improve it: try to have as few lines as possible in the "try" block, and add an "else" block for what should happen when the exception is not raised. This can reduce code duplication, and more importantly, communicate to the reader what your intent is. Here is a good SO question explaining try/except/else and why you might use it: http://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python – Frank T Feb 11 '14 at 14:45

2 Answers2

4

You can use the get method of the dictionary with the optional default argument:

d = {"a":'letter', 4:'number'}
result = d.get("not in d", 'error')
print result
# error

It's also common in python to use try/except loops to check for key errors, though it's probably overkill for this case.

d = {"a":'letter', 4:'number'}
try:
    result = d['not in d']
except KeyError:
    result = 'error'
print result
# error

I think the easiest way to check if a string is a number in python is is to use either float or int inside a try except block, much like the second example above. Which one you use will depend on whether you want to consider "1.2" and "3.4e5" as numbers or not.

Bi Rico
  • 25,283
  • 3
  • 52
  • 75
0

I think you'll find this "open-endedness" by checking whether your item is in the dictionary. Something like this will work:

my_item in lexicon

This returns True if the dictionary lexicon contains a key of my_item, and False otherwise.

But there is a better way to do it.

This is a good opportunity to practice Python's concept of "It's Better To Ask Forgiveness Than Permission". How about something like:

try:
    value = lexicon[my_item]
except KeyError:
    # KeyError is thrown when you index into a dictionary with a nonexistent key
    value = 'error'

Coincidentally, you can do the same sort of thing to check whether a string can be converted to an integer.

try:
    value = int(maybe_a_number)
except ValueError:
    # maybe_a_number wasn't a number!

This is a common pattern in Python -- try to use a variable in a certain way, and handle the failure case.

For more material on dictionaries, check out: http://docs.python.org/2/library/stdtypes.html#mapping-types-dict

Frank T
  • 8,268
  • 8
  • 50
  • 67
  • It would be far better to use a default value in this case; `value = lexicon.get(my_item, "error")` – Daenyth Feb 10 '14 at 23:32