1

Possible Duplicate:
Inverse dictionary lookup - Python

Is there a built in way to index a dictionary by value in Python.

e.g. something like:

dict = {'fruit':'apple','colour':'blue','meat':'beef'}
print key where dict[key] == 'apple'

or:

dict = {'fruit':['apple', 'banana'], 'colour':'blue'}
print key where 'apple' in dict[key]

or do I have to manually loop it?

Community
  • 1
  • 1
Ferguzz
  • 5,777
  • 7
  • 34
  • 41
  • 2
    Be warned that there is no restriction that the `values` be unique in the `dict`, only the `keys`. What happens when you have two identical keys in your dict? – Hooked Apr 04 '12 at 16:44
  • 2
    @Hooked Do you mean "two identical values"? – Kris Harper Apr 04 '12 at 16:45
  • @root45 yes I mean identical "values" not "keys", sorry about that. Something like: `A = {1:'foo',2:'foo'}`. What is the inverse of `foo` supposed to return here? – Hooked Apr 04 '12 at 16:46
  • 1
    I suppose a list of keys should be returned in that case. good point though. – Ferguzz Apr 04 '12 at 16:47
  • @Ferguzz: Wrote you an answer which works in all cases and returns a list of keys so that duplicates are handled properly. – Niklas B. Apr 04 '12 at 17:03
  • @casperOne: I don't agree. This problem is a more complex than the "duplicate" – Niklas B. Apr 06 '12 at 15:12

3 Answers3

5

You could use a list comprehension:

my_dict = {'fruit':'apple','colour':'blue','meat':'beef'}
print [key for key, value in my_dict.items() if value == 'apple']

The code above is doing almost exactly what said you want:

print key where dict[key] == 'apple'

The list comprehension is going through all the key, value pairs given by your dictionary's items method, and making a new list of all the keys where the value is 'apple'.

As Niklas pointed out, this does not work when your values could potentially be lists. You have to be careful about just using in in this case since 'apple' in 'pineapple' == True. So, sticking with a list comprehension approach requires some type checking. So, you could use a helper function like:

def equals_or_in(target, value):
    """Returns True if the target string equals the value string or,
    is in the value (if the value is not a string).
    """
    if isinstance(target, str):
        return target == value
    else:
        return target in value

Then, the list comprehension below would work:

my_dict = {'fruit':['apple', 'banana'], 'colour':'blue'}
print [key for key, value in my_dict.items() if equals_or_in('apple', value)]
Wilduck
  • 13,822
  • 10
  • 58
  • 90
  • I believe OP wants to search the values not the keys. – Amjith Apr 04 '12 at 16:53
  • @Amjith Yeah, I had a brain fart. It's been fixed. – Wilduck Apr 04 '12 at 16:54
  • Doesn't work in the more general second example – Niklas B. Apr 04 '12 at 16:56
  • Rather than checking for string type, I'd check for list type, which is less likely to change, otherwise your function will not work for integer values, for example. Also, you should use `isinstance` instance of comparing `type()`s – Niklas B. Apr 04 '12 at 17:31
  • The issue with checking for a `list` type is that you no longer can check for membership in a set. Where the correct answer falls is up to the OP. You're definitely right about `isinstance` though. I'll update. – Wilduck Apr 04 '12 at 18:20
4

You'll have to manually loop it, but if you'll need the lookup repeatedly this is a handy trick:

d1 = {'fruit':'apple','colour':'blue','meat':'beef'}

d1_rev = dict((v, k) for k, v in d1.items())

You can then use the reverse dictionary like this:

>>> d1_rev['blue']
'colour'
>>> d1_rev['beef']
'meat'
Niklas B.
  • 92,950
  • 18
  • 194
  • 224
g.d.d.c
  • 46,865
  • 9
  • 101
  • 111
3

Your requirements are more complex than you realize:

  • You need to handle both list values and plain values
  • You don't actually need to get back a key, but a list of keys

You could solve this in two steps:

  1. normalize the dict so that every value is a list (every plain value becomes a single-element)
  2. build a reverse dictionary

The following functions will solve this:

from collections import defaultdict

def normalize(d):
    return { k:(v if isinstance(v, list) else [v]) for k,v in d.items() }

def build_reverse_dict(d):
    res = defaultdict(list)
    for k,values in normalize(d).items():
        for x in values:
            res[x].append(k)
    return dict(res)

To be used like this:

>>> build_reverse_dict({'fruit':'apple','colour':'blue','meat':'beef'})
{'blue': ['colour'], 'apple': ['fruit'], 'beef': ['meat']}
>>> build_reverse_dict({'fruit':['apple', 'banana'], 'colour':'blue'})
{'blue': ['colour'], 'apple': ['fruit'], 'banana': ['fruit']}
>>> build_reverse_dict({'a':'duplicate', 'b':['duplicate']})
{'duplicate': ['a', 'b']}

So you just build up the reverse dictionary once and then lookup by value and get back a list of keys.

Niklas B.
  • 92,950
  • 18
  • 194
  • 224