reverse dict lookup & Relation class

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Jan 16 06:03:24 EST 2009


On Wed, 14 Jan 2009 16:30:36 -0800, Aaron Brady wrote:

> Hi, this is a continuation of something that comes up now and again
> about reverse lookups on dictionaries, as well as a follow-up to my
> pursuit of a Relation class from earlier.

[...]

> What's the best way to construct this class?  Or, do you have an
> argument that it could not be simpler than using a relational db?
> Brainstorming, not flamestorming.

Here's a lightweight solution that uses lazy calculation of the reverse 
dict. It makes a number of assumptions:

- both keys and values will be hashable and unique;
- the dict won't be so large that calculating the reverse dictionary will 
be time consuming;
- modifications to instance.reverse are undefined.


Usage:


>>> d = ReverseDict(x=2, y=3, z=4)
>>> d['x']
2
>>> d.reverse[2]
'x'
>>> d['a'] = 0
>>> d.reverse[0]
'a'





def make_dirty(func):
    from functools import wraps
    @wraps(func)
    def f(self, *args, **kwargs):
        self._dirty = True
        return func(self, *args, **kwargs)
    return f


# only tested a little bit
class ReverseDict(dict):
    def __init__(self, *args, **kwargs):
        super(ReverseDict, self).__init__(*args, **kwargs)
        self._dirty = True
    @make_dirty
    def clear():
        super(ReverseDict, self).clear()
    @make_dirty
    def __setitem__(self, key, value):
        super(ReverseDict, self).__setitem__(key, value)
    @make_dirty
    def __delitem__(self, key):
        super(ReverseDict, self).__delitem__()
    @make_dirty
    def pop(self, key, *arg):
        return super(ReverseDict, self).pop(key, *arg)
    @make_dirty
    def popitem(self):
        return super(ReverseDict, self).popitem()
    @make_dirty
    def setdefault(self, key, value=None):
        return super(ReverseDict, self).setdefault(key, value)
    @make_dirty
    def update(self, other):
        return super(ReverseDict, self).update(other)
    @property
    def reverse(self):
        if self._dirty:
            # Modify this to support repeated and non-hashable values.
            self._reverse = dict((value, key) for 
            (key, value) in self.iteritems())
            self._dirty = False
        return self._reverse




An even more lightweight solution is to give up O(1) for the reverse 
lookup:


# untested
class ReversableDict(dict):
    def lookupvalue(self, value):
        for k,v in self.iteritems():
            if v == value: return k
        raise ValueError('value not found')
    def lookupallvalues(self, value):
        results = []
        for k,v in self.iteritems():
            if v == value: results.append(k)
        return results




-- 
Steven



More information about the Python-list mailing list