141

Is there any straightforward way of finding a key by knowing the value within a dictionary?

All I can think of is this:

key = [key for key, value in dict_obj.items() if value == 'value'][0]
4
  • possible duplicate: stackoverflow.com/questions/483666/… Commented May 28, 2013 at 13:26
  • 1
    Google guided me here... And i must say.. why is no one using iteritems as for me this makes a 40x faster difference... using the ().next method
    – Angry 84
    Commented Oct 19, 2016 at 5:56
  • 8
    If you have a lot of reverse lookups to do: reverse_dictionary = {v:k for k,v in dictionary.items()}
    – Austin
    Commented Jul 6, 2018 at 21:33
  • @TobiasKienzler while the problem certainly could be solved by inverting the dictionary and then doing a normal lookup, the apparent goal here is simply to determine which key corresponds to a certain value. There are other approaches to this, although they will still be O(N). Commented May 12, 2023 at 4:56

13 Answers 13

136

Your list comprehension goes through all the dict's items finding all the matches, then just returns the first key. This generator expression will only iterate as far as necessary to return the first value:

key = next(key for key, value in dd.items() if value == 'value')

where dd is the dict. Will raise StopIteration if no match is found, so you might want to catch that and return a more appropriate exception like ValueError or KeyError.

3
  • 2
    Yes It should probably raise same exception as listObject.index(key) when key is not in the list.
    – Brian Jack
    Commented Jul 25, 2012 at 19:48
  • 7
    also keys = { key for key,value in dd.items() if value=='value' } to get the set of all keys if several matches.
    – askewchan
    Commented May 16, 2013 at 19:57
  • 7
    @askewchan - no real need to return this as a set, dict keys already have to be unique, just return a list - or better, return a generator expression, and let the caller put it in whatever container they want.
    – PaulMcG
    Commented May 19, 2013 at 16:17
72

There are cases where a dictionary is a one:one mapping

Eg,

d = {1: "one", 2: "two" ...}

Your approach is ok if you are only doing a single lookup. However if you need to do more than one lookup it will be more efficient to create an inverse dictionary

ivd = {v: k for k, v in d.items()}

If there is a possibility of multiple keys with the same value, you will need to specify the desired behaviour in this case.

If your Python is 2.6 or older, you can use

ivd = dict((v, k) for k, v in d.items())
4
  • 7
    Nice optimization. But, I think you meant to turn your list of 2-tuples into a dictionary using dict(): ivd=dict([(v,k) for (k,v) in d.items()])
    – hobs
    Commented Jul 25, 2012 at 20:44
  • 4
    @hobs just use a dict comprehension instead of list comprehension: invd = { v:k for k,v in d.items() }
    – askewchan
    Commented May 16, 2013 at 19:50
  • @gnibbler dict comprehensions haven't been migrated back to Python 2.6, so if you want to remain portable you'll need to put up with the 6 extra characters for dict() around a generator of 2-tuples or a list comprehension of 2-tuples
    – hobs
    Commented May 16, 2013 at 22:23
  • @hobs, I added that to my answer. Commented May 17, 2013 at 2:19
33

This version is 26% shorter than yours but functions identically, even for redundant/ambiguous values (returns the first match, as yours does). However, it is probably twice as slow as yours, because it creates a list from the dict twice.

key = dict_obj.keys()[dict_obj.values().index(value)]

Or if you prefer brevity over readability you can save one more character with

key = list(dict_obj)[dict_obj.values().index(value)]

And if you prefer efficiency, @PaulMcGuire's approach is better. If there are lots of keys that share the same value it's more efficient not to instantiate that list of keys with a list comprehension and instead use use a generator:

key = (key for key, value in dict_obj.items() if value == 'value').next()
2
  • 2
    Assuming an atomic operation, are keys and values guaranteed to be in the same corresponding order? Commented May 25, 2016 at 14:09
  • 1
    @NoctisSkytower Yes, dict.keys() and dict.values() are guaranteed to correspond as long as the dict isn't mutated between calls.
    – hobs
    Commented May 25, 2016 at 16:07
8

Since this is still very relevant, the first Google hit and I just spend some time figuring this out, I'll post my (working in Python 3) solution:

testdict = {'one'   : '1',
            'two'   : '2',
            'three' : '3',
            'four'  : '4'
            }

value = '2'

[key for key in testdict.items() if key[1] == value][0][0]

Out[1]: 'two'

It will give you the first value that matches.

6

Maybe a dictionary-like class such as DoubleDict down below is what you want? You can use any one of the provided metaclasses in conjuction with DoubleDict or may avoid using any metaclass at all.

import functools
import threading

################################################################################

class _DDChecker(type):

    def __new__(cls, name, bases, classdict):
        for key, value in classdict.items():
            if key not in {'__new__', '__slots__', '_DoubleDict__dict_view'}:
                classdict[key] = cls._wrap(value)
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def check(self, *args, **kwargs):
            value = function(self, *args, **kwargs)
            if self._DoubleDict__forward != \
               dict(map(reversed, self._DoubleDict__reverse.items())):
                raise RuntimeError('Forward & Reverse are not equivalent!')
            return value
        return check

################################################################################

class _DDAtomic(_DDChecker):

    def __new__(cls, name, bases, classdict):
        if not bases:
            classdict['__slots__'] += ('_DDAtomic__mutex',)
            classdict['__new__'] = cls._atomic_new
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _atomic_new(cls, iterable=(), **pairs):
        instance = object.__new__(cls, iterable, **pairs)
        instance.__mutex = threading.RLock()
        instance.clear()
        return instance

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def atomic(self, *args, **kwargs):
            with self.__mutex:
                return function(self, *args, **kwargs)
        return atomic

################################################################################

class _DDAtomicChecker(_DDAtomic):

    @staticmethod
    def _wrap(function):
        return _DDAtomic._wrap(_DDChecker._wrap(function))

################################################################################

class DoubleDict(metaclass=_DDAtomicChecker):

    __slots__ = '__forward', '__reverse'

    def __new__(cls, iterable=(), **pairs):
        instance = super().__new__(cls, iterable, **pairs)
        instance.clear()
        return instance

    def __init__(self, iterable=(), **pairs):
        self.update(iterable, **pairs)

    ########################################################################

    def __repr__(self):
        return repr(self.__forward)

    def __lt__(self, other):
        return self.__forward < other

    def __le__(self, other):
        return self.__forward <= other

    def __eq__(self, other):
        return self.__forward == other

    def __ne__(self, other):
        return self.__forward != other

    def __gt__(self, other):
        return self.__forward > other

    def __ge__(self, other):
        return self.__forward >= other

    def __len__(self):
        return len(self.__forward)

    def __getitem__(self, key):
        if key in self:
            return self.__forward[key]
        return self.__missing_key(key)

    def __setitem__(self, key, value):
        if self.in_values(value):
            del self[self.get_key(value)]
        self.__set_key_value(key, value)
        return value

    def __delitem__(self, key):
        self.pop(key)

    def __iter__(self):
        return iter(self.__forward)

    def __contains__(self, key):
        return key in self.__forward

    ########################################################################

    def clear(self):
        self.__forward = {}
        self.__reverse = {}

    def copy(self):
        return self.__class__(self.items())

    def del_value(self, value):
        self.pop_key(value)

    def get(self, key, default=None):
        return self[key] if key in self else default

    def get_key(self, value):
        if self.in_values(value):
            return self.__reverse[value]
        return self.__missing_value(value)

    def get_key_default(self, value, default=None):
        return self.get_key(value) if self.in_values(value) else default

    def in_values(self, value):
        return value in self.__reverse

    def items(self):
        return self.__dict_view('items', ((key, self[key]) for key in self))

    def iter_values(self):
        return iter(self.__reverse)

    def keys(self):
        return self.__dict_view('keys', self.__forward)

    def pop(self, key, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if key in self:
            value = self[key]
            self.__del_key_value(key, value)
            return value
        if default:
            return default[0]
        raise KeyError(key)

    def pop_key(self, value, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if self.in_values(value):
            key = self.get_key(value)
            self.__del_key_value(key, value)
            return key
        if default:
            return default[0]
        raise KeyError(value)

    def popitem(self):
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError('popitem(): dictionary is empty')
        return key, self.pop(key)

    def set_key(self, value, key):
        if key in self:
            self.del_value(self[key])
        self.__set_key_value(key, value)
        return key

    def setdefault(self, key, default=None):
        if key not in self:
            self[key] = default
        return self[key]

    def setdefault_key(self, value, default=None):
        if not self.in_values(value):
            self.set_key(value, default)
        return self.get_key(value)

    def update(self, iterable=(), **pairs):
        for key, value in (((key, iterable[key]) for key in iterable.keys())
                           if hasattr(iterable, 'keys') else iterable):
            self[key] = value
        for key, value in pairs.items():
            self[key] = value

    def values(self):
        return self.__dict_view('values', self.__reverse)

    ########################################################################

    def __missing_key(self, key):
        if hasattr(self.__class__, '__missing__'):
            return self.__missing__(key)
        if not hasattr(self, 'default_factory') \
           or self.default_factory is None:
            raise KeyError(key)
        return self.__setitem__(key, self.default_factory())

    def __missing_value(self, value):
        if hasattr(self.__class__, '__missing_value__'):
            return self.__missing_value__(value)
        if not hasattr(self, 'default_key_factory') \
           or self.default_key_factory is None:
            raise KeyError(value)
        return self.set_key(value, self.default_key_factory())

    def __set_key_value(self, key, value):
        self.__forward[key] = value
        self.__reverse[value] = key

    def __del_key_value(self, key, value):
        del self.__forward[key]
        del self.__reverse[value]

    ########################################################################

    class __dict_view(frozenset):

        __slots__ = '__name'

        def __new__(cls, name, iterable=()):
            instance = super().__new__(cls, iterable)
            instance.__name = name
            return instance

        def __repr__(self):
            return 'dict_{}({})'.format(self.__name, list(self))
0
6

Make a reverse dictionary

reverse_dictionary = {v:k for k,v in dictionary.items()} 

If you have a lot of reverse lookups to do

2
  • 4
    This works only when there's a 1:1 mapping between keys and values.
    – Noel Yap
    Commented Aug 24, 2020 at 23:25
  • This also requires the values (which now become keys) to be hashable.
    – MMM
    Commented Oct 31, 2022 at 10:50
6
# oneline solution using zip
>> x = {'a':100, 'b':999}
>> y = dict(zip(x.values(), x.keys()))  
>> y
{100: 'a', 999: 'b'}
5

No, you can not do this efficiently without looking in all the keys and checking all their values. So you will need O(n) time to do this. If you need to do a lot of such lookups you will need to do this efficiently by constructing a reversed dictionary (can be done also in O(n)) and then making a search inside of this reversed dictionary (each search will take on average O(1)).

Here is an example of how to construct a reversed dictionary (which will be able to do one to many mapping) from a normal dictionary:

for i in h_normal:
    for j in h_normal[i]:
        if j not in h_reversed:
            h_reversed[j] = set([i])
        else:
            h_reversed[j].add(i)

For example if your

h_normal = {
  1: set([3]), 
  2: set([5, 7]), 
  3: set([]), 
  4: set([7]), 
  5: set([1, 4]), 
  6: set([1, 7]), 
  7: set([1]), 
  8: set([2, 5, 6])
}

your h_reversed will be

{
  1: set([5, 6, 7]),
  2: set([8]), 
  3: set([1]), 
  4: set([5]), 
  5: set([8, 2]), 
  6: set([8]), 
  7: set([2, 4, 6])
}
2

There isn't one as far as I know of, one way however to do it is to create a dict for normal lookup by key and another dict for reverse lookup by value.

There's an example of such an implementation here:

http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/

This does mean that looking up the keys for a value could result in multiple results which can be returned as a simple list.

1
  • Do note that there are many, many possible values that are not valid keys. Commented Apr 2, 2010 at 19:28
1

I know this might be considered 'wasteful', but in this scenario I often store the key as an additional column in the value record:

d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) }

it's a tradeoff and feels wrong, but it's simple and works and of course depends on values being tuples rather than simple values.

0
0

Through values in dictionary can be object of any kind they can't be hashed or indexed other way. So finding key by the value is unnatural for this collection type. Any query like that can be executed in O(n) time only. So if this is frequent task you should take a look for some indexing of key like Jon sujjested or maybe even some spatial index (DB or http://pypi.python.org/pypi/Rtree/ ).

-1

I'm using dictionaries as a sort of "database", so I need to find a key that I can reuse. For my case, if a key's value is None, then I can take it and reuse it without having to "allocate" another id. Just figured I'd share it.

db = {0:[], 1:[], ..., 5:None, 11:None, 19:[], ...}

keys_to_reallocate = [None]
allocate.extend(i for i in db.iterkeys() if db[i] is None)
free_id = keys_to_reallocate[-1]

I like this one because I don't have to try and catch any errors such as StopIteration or IndexError. If there's a key available, then free_id will contain one. If there isn't, then it will simply be None. Probably not pythonic, but I really didn't want to use a try here...

-11

There is none. Don't forget that the value may be found on any number of keys, including 0 or more than 1.

6
  • 2
    python has a .index method on lists the returns the first found index with the specified value or an exception if not found... any reason why such a semantic could not be applied to dictionaries?
    – Brian Jack
    Commented Jun 22, 2012 at 15:27
  • @BrianJack: Dictionaries are not ordered, like sets. Look at collections.OrderedDict for an implementation that is ordered. Commented Jul 24, 2012 at 14:40
  • 3
    .index only needs to guarantee that it returns a single value and it does not need to be lexically first only that it be the first match and that it's behavior is stable (multiple calls on same dict over time should yield same matching element). Unless dictionaries rearrange their unmodified hashes over time as other elements get added, removed or modified it would still work suitably. A naive implementation: dictObject.items().index(key)
    – Brian Jack
    Commented Jul 25, 2012 at 19:43
  • 162
    I abhor non-answers like this. "Stop trying to do what you justifiably want to do!" is not an acceptable answer. Why was this accepted? As higher-rated answers to this question attest, reverse dictionary lookup is trivially implementable in less than 80 characters of pure-Python. It doesn't get any more "straight forward" than that. Paul McGuire's solution is probably the most efficient, but they all work. </sigh> Commented Jun 28, 2016 at 2:04
  • 1
    Including the above response from @CecilCurry in Python3 (3.x?) Dictionaries are now ordered, as with sets. (though sets will be a random order every time you create one, a single instance will not rearrange. Not sure about Dictionaries but imagine it's similar.)
    – ch4rl1e97
    Commented Mar 26, 2020 at 15:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.