Recipe4.15.Associating Multiple Values with Each Key in a Dictionary


Recipe 4.15. Associating Multiple Values with Each Key in a Dictionary

Credit: Michael Chermside

Problem

You need a dictionary that maps each key to multiple values.

Solution

By nature, a dictionary is a one-to-one mapping, but it's not hard to make it one-to-manyin other words, to make one key map to multiple values. Your choice of one of two possible approaches depends on how you want to treat duplications in the set of values for a key. The following approach, based on using lists as the dict's values, allows such duplications:

d1 = {  } d.setdefault(key, [  ]).append(value)

while an alternative approach, based on using sub-dicts as the dict's values, automatically eliminates duplications of values:

d2 = {  } d2.setdefault(key, {  })[value] = 1

In Python 2.4, the no-duplication approach can equivalently be coded:

d3 = {  } d3.setdefault(key, set( )).add(value)

Discussion

A normal dictionary performs a simple mapping of each key to one value. This recipe shows three easy, efficient ways to achieve a mapping of each key to multiple values, by holding as the dictionary's values lists, sub-dicts, or, in Python 2.4, sets. The semantics of the list-based approach differ slightly but importantly from those of the other two in terms of how they deal with duplication. Each approach relies on the setdefault method of a dictionary, covered earlier in Recipe 4.10, to initialize the entry for a key in the dictionary, if needed, and in any case to return said entry.

You need to be able to do more than just add values for a key. With the first approach, which uses lists and allows duplications, here's how to retrieve the list of values for a key:

list_of_values = d1[key]

Here's how to remove one value for a key, if you don't mind leaving empty lists as items of d1 when the last value for a key is removed:

d1[key].remove(value)

Despite the empty lists, it's still easy to test for the existence of a key with at least one valuejust use a function that always returns a list (maybe an empty one), such as:

def get_values_if_any(d, key):     return d.get(key, [  ])

For example, to check whether 'freep' is among the values (if any) for key 'somekey' in dictionary d1, you can code: if 'freep' in get_values_if_any(d1, 'somekey').

The second approach, which uses sub-dicts and eliminates duplications, can use rather similar idioms. To retrieve the list of values for a key:

list_of_values = list(d2[key])

To remove one value for a key, leaving empty dictionaries as items of d2 when the last value for a key is removed:

del d2[key][value]

In the third approach, showing the Python 2.4-only version d3, which uses sets, this would be:

d3[key].remove(value)

One possibility for the get_values_if_any function in either the second or third (duplication-removing) approaches would be:

def get_values_if_any(d, key):     return list(d.get(key, ( )))

This recipe focuses on how to code the raw functionality, but, to use this functionality in a systematic way, you'll probably want to wrap up this code into a class. For that purpose, you need to make some of the design decisions that this recipe highlights. Do you want a value to be in the entry for a key multiple times? (Is the entry for each key a bag rather than a set, in mathematical terms?) If so, should remove just reduce the number of occurrences by 1, or should it wipe out all of them? This is just the beginning of the choices you have to make, and the right choices depend on the specifics of your application.

See Also

Recipe 4.10; the Library Reference and Python in a Nutshell sections on mapping types; Recipe 18.8 for an implementation of the bag type.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net