4

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.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Sorry, I wrote it wrong originally. I want a surjective mapping, not an injective one. MultiDict allows multiple values per key, not multiple keys per value, and also has no primary keys. – datenwolf Nov 28 '11 at 18:24

3 Answers3

2

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.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
1

The simplest and easiest solution would be to use two dictionaries, one of which maps secondary keys to a primary key. If for some reason you need a reverse mapping, that could be included in the primary dictionary.

sec = {'one': 'blue', 'two': 'red', 'three': 'blue',   # seconary keys
       'blue': 'blue', 'red': 'red'}                   # include identity mapping for primaries
dict = {'blue': ('doll', '$9.43', ('one', 'three')),
        'red':  ('truck', '$14.99', ('two',)) }

record = dict[sec['two']]
print('Toy=', record[0], 'Price=', record[1])
Dave
  • 3,834
  • 2
  • 29
  • 44
  • Well, this is how I'm currently doing it. I was just wondering, if there were some "batteries", maybe even optimized, that I could just use – before I go and wrap that (your suggestion and my current method) into Q'n'D class. – datenwolf Nov 29 '11 at 10:32
  • It's an interesting idea to test if a new class would be more efficient. "All problems in computer science can be solved by another level of indirection" (--David Wheeler), and since your original indirection idea is so transparent and compact, I'd go with it unless there are significant gains from using a more complex method. – Dave Nov 29 '11 at 12:44
  • The main reason for this is, that I want to be able to pass around a reference to that "dict" instead of dereferencing with a member function of the class containing the dict. The whole thing is going to be a motor control interface. On a single bus multiple drives/axes can be connected. The bus is enumerated (the enumeration function is given int->string dictionary, axes names as "Phi" or "X"), and ATM axes are retrieved by `axis(IdOrName)` member function of the bus class. I'd rather have a `axes` element, that I can use like a dict, i.e. use it as (key, value) iterable in a for loop. – datenwolf Nov 29 '11 at 13:07
0

There is now a multiple key dictionary python package.
https://pypi.python.org/pypi/multi_key_dict/1.0.2

From the link:

from multi_key_dict import multi_key_dict

k = multi_key_dict()
k[1000, 'kilo', 'k'] = 'kilo (x1000)'

print k[1000] # will print 'kilo (x1000)'
print k['k'] # will also print 'kilo (x1000)'

# the same way objects can be updated, deleted:
# and if an object is updated using one key, the new value will
# be accessible using any other key, e.g. for example above:
k['kilo'] = 'kilo'
print k[1000] # will now print 'kilo' as value was updated
sickgemini
  • 509
  • 2
  • 6
  • 16