pythondictionaryprimary-keycontainersmultikey

Python dict like surjective multiple key → value container


I'm currently in the need for a Python container class with similar functionality like the builtin dict type. Basically what I need is a dictionary, where an arbitrary number of keys beside a primary key, which map to the very same value. However when iterating over it, it should iterate only over the (primary_key, value) pairs and only the primary key if the list of keys is requested.

If this has already been implemented I'd rather not reinvent the wheel. So is there already a module providing such a container? If not, I'm going to implement it myself.


Solution

  • Here is a quick implementation:

    class MultipleKeyDict(dict):
        __slots__ = ["_primary_keys"]
        def __init__(self, arg=None, **kwargs):
            self._primary_keys = {}
            self.update(arg, **kwargs)
        def __setitem__(self, key, value):
            super(MultipleKeyDict, self).__setitem__(key, value)
            self._primary_keys.setdefault(value, key)
        def __delitem__(self, key):
            value = self[key]
            super(MultipleKeyDict, self).__delitem__(key)
            if self._primary_keys[value] == key:
                del self._primary_keys[value]
                for k, v in super(MultipleKeyDict, self).iteritems():
                    if v == value:
                        self._primary_keys[value] = k
                        break
        def __iter__(self):
            return self.iterkeys()
        def update(self, arg=None, **kwargs):
            if arg is not None:
                if isinstance(arg, collections.Mapping):
                    for k in arg:
                        self[k] = arg[k]
                else:
                    for k, v in arg:
                        self[k] = v
            for k in kwargs:
                self[k] = kwargs[k]
        def clear(self):
            super(MultipleKeyDict, self).clear()
            self._primary_keys.clear()
        def iteritems(self):
            for v, k in self._primary_keys.iteritems():
                yield k, v
        def items(self):
            return list(self.iteritems())
        def itervalues(self):
            return self._primary_keys.iterkeys()
        def values(self):
            return self._primary_keys.keys()
        def iterkeys(self):
            return self._primary_keys.itervalues()
        def keys(self):
            return self._primary_keys.values()
    

    The only messy bit is that it has to search the whole dict in case a primary key gets deleted.

    I omitted copy(), pop(), popitem() and setdefault(). If you need them, you'll have to implement them yourself.