1.6 Associating Multiple Values with Each Key in a Dictionary


Credit: Michael Chermside

1.6.1 Problem


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


1.6.2 Solution


By nature, a dictionary is a one-to-one mapping, but it's not hard to make it one-to-many梚n other words, to make one key map to multiple values. There are two possible approaches, depending on how you want to treat duplications in the set of values for a key. The following approach allows such duplications:


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

while this approach automatically eliminates duplications:


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

1.6.3 Discussion


A normal dictionary performs a simple mapping of a key to a value. This recipe shows two easy, efficient ways to achieve a mapping of each key to multiple values. The semantics of the two approaches differ slightly but importantly in how they deal with duplication. Each approach relies on the setdefault method of a dictionary to initialize the entry for a key in the dictionary, if needed, and in any case to return said entry.


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

当然,你需要能够做的比仅仅为一个键增加一个值更多。 对于第一种方法,它允许有重复的键,这儿是怎样为一个键去获取值的列表:

   1 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:


   1 d1[key].remove(value) 

Despite the empty lists, it's still easy to test for the existence of a key with at least one value:


   1 def has_key_with_some_values(d, key): 
   2     return d.has_key(key) and d[key] 

This returns either 0 or a list, which may be empty. In most cases, it is easier to use a function that always returns a list (maybe an empty one), such as:


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

You can use either of these functions in a statement. For example:


   1 if get_values_if_any(d1, somekey): 
   3 if has_key_with_some_values(d1, somekey): 

However, get_values_if_any is generally handier. For example, you can use it to check if 'freep' is among the values for somekey:


   1 if 'freep' in get_values_if_any(d1, somekey): 

This extra handiness comes from get_values_if_any always returning a list, rather than sometimes a list and sometimes 0.

这种便利来自于get_values_if_any 一直返回一个列表而不是有时候返回一个列表,有时候返回0

The first approach allows each value to be present multiple times for each given key. For example:


   1 example = {} 
   2 example.setdefault('a', []).append('apple') 
   3 example.setdefault('b', []).append('boots') 
   4 example.setdefault('c', []).append('cat') 
   5 example.setdefault('a', []).append('ant') 
   6 example.setdefault('a', []).append('apple') 

Now example['a'] is ['apple', 'ant', 'apple']. If we now execute:

现在, example['a']是['apple', 'ant', 'apple']。 如果我们现在执行:

   1 example['a'].remove('apple') 

the following test is still satisfied:


if 'apple' in example['a']

'apple' was present twice, and we removed it only once. (Testing for 'apple' with get_values_if_any(example, 'a') would be more general, although equivalent in this case.)

'apple'出现两次,并且我们仅仅移除一次。(为'apple', 做get_values_if_any(example, 'a')测试尽管相当于这种情况,但是更具普遍性)

The second approach, which eliminates duplications, requires rather similar idioms. Here's how to retrieve the list of the values for a key:


list_of_values = d2[key].keys( ) 

Here's how to remove a key/value pair, leaving empty dictionaries as items of d2 when the last value for a key is removed:


del d2[key][value] 

The has_key_with_some_values function shown earlier also works for the second approach, and you also have analogous alternatives, such as:

has_key_with_some_values 函数象更早显示的那样为第二种方法工作,你也有一个相似的选择,诸如:

   1 def get_values_if_any(d, key): 
   2     return d.get(key, {}).keys( ) 

The second approach doesn't allow duplication. For example:


   1 example = {} 
   2 example.setdefault('a', {})['apple']=1 
   3 example.setdefault('b', {})['boots']=1 
   4 example.setdefault('c', {})['cat']=1 
   5 example.setdefault('a', {})['ant']=1 
   6 example.setdefault('a', {})['apple']=1 

Now example['a'] is {'apple':1, 'ant':1}. Now, if we execute:

现在example['a'] 是 {'apple':1, 'ant':1}。 现在,如果我们执行:

   1 del example['a']['apple'] 

the following test is not satisfied:


if 'apple' in example['a'] 

'apple' was present, but we just removed it.


This recipe focuses on how to code the raw functionality, but if you want to use this functionality in a systematic way, you'll want to wrap it up in a class. For that purpose, you need to make some of the design decisions that the recipe highlights. Do you want a value to be in the entry for a key multiple times? (Is the entry 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.


1.6.4 See Also


The Library Reference section on mapping types.

PyCkBk-1-6 (last edited 2009-12-25 07:15:35 by localhost)