[Tutor] value range checker

Peter Otten __peter__ at web.de
Thu Aug 27 09:48:46 CEST 2015

Albert-Jan Roskam wrote:

> I have a written a function checks the validity of values. The ranges of
> valid values are stored in a database table.
> Such a table contains three columns: category, min and max. One record of
> such a table specifies the range for
> a certain category, but a category may be spread out over multiple
> records.

> My questions:

> def get_valid_value_lookup(records):
>     """
>     Translates value range information (from a database table)
>     into a dictionary of the form {<category>: [<range of accepted
>     values>]} """
>     Boundaries = collections.namedtuple("Boundaries", "category min max")
>     records = [Boundaries(*record) for record in records]
>     boundaries = collections.defaultdict(list)
>     crap = [boundaries[record.category].__iadd__(range(record.min,
>     record.max + 1))
>             for record in records]
>     return dict(boundaries)

> [1] @ is_valid: is there a better way to do this? I mostly don't like the
> [use of the __iadd__ dunder method.

When you find yourself using a list comprehension for its side effect it is 
high time to switch to a for loop. So

def get_valid_value_lookup(records):
    boundaries = {}
    for category, minval, maxval in records:
        boundaries.setdefault(category, []).extend(range(minval, maxval+1))
    return boundaries

> [2] @ is_valid2: Perhaps an answer to my previous question. Is this a
> [better approach?

As long as the size of the ranges is manageable and you are not actually 
seeing float values I'd stick with the dead simple first version.
Replace the list in get_valid_value_lookup() with a set 

boundaries.setdefault(category, set()).update(range(minval, maxval+1))

and you get O(1) lookup for free.

> def is_valid2(lookup, category, value):
>     """Return True if value is member of a list of a given category, False
>     otherwise."""
>     # this version also knows how to deal with floats.
>     try:
>          L = lookup[category]
>     except KeyError:
>         raise KeyError("Invalid category: %r" % category)
>     adjusted_value = value if int(value) in (L[0], 0, L[-1]) else
>     math.ceil(value) 
>     try:
>         chopfunc = bisect.bisect_right if value < L[0] else
>         bisect.bisect_left return L[chopfunc(L, value)] == adjusted_value
>     except IndexError:
>         return False

I don't understand what this is supposed to do:

(1) for bisect to work correctly the list has to be sorted. Your 
get_valid_value_lookup() doesn't do that

(2) Why/how are L[0], 0, and L[-1] special?

(3) Given a sorted list L there should be no need to bisect_whatever for
value < L[0]

> [3] I am inheriting a this system. It feels a bit strange that these range
> [check values are stored in a database.
> Would yaml be a better choice? Some of the tables are close to 200
> records.

Unless you're encountering an actual inconvenience just keep it like it is. 
Yea, I'm a lazy bastard ;)

More information about the Tutor mailing list