2010-04-02 2 views
68

Есть ли простой способ найти ключ, зная значение в словаре?Поиск в исходном словаре в Python

Все, что я могу думать, это:

key = [key for key, value in dict_obj.items() if value == 'value'][0] 
+0

Возможный дубликат: http://stackoverflow.com/questions/483666/python-reverse-inverse-a-mapping –

+0

Взгляните на мой ответ [как создать обратный словарь] (http://stackoverflow.com)/a/27052594/1090562) –

+0

Google направил меня сюда ... И я должен сказать ... почему никто не использует 'iteritems', так как для меня это делает разницу в 40 раз быстрее ... с использованием метода() .next – Mayhem

ответ

25

Там нет. Не забывайте, что значение может быть найдено на любом количестве ключей, в том числе 0 или больше, чем 1.

+0

python имеет метод .index для списков возвращает первый найденный индекс с указанным значением или исключение, если не найден ... любая причина, почему такая семантика не может быть применена к словарям? –

+0

@BrianJack: словари не упорядочены, как наборы. Посмотрите на collection.OrderedDict для реализации, которая * * упорядочена. –

+2

. Индекс должен только гарантировать, что он возвращает одно значение, и ему не нужно лексически сначала только, чтобы оно было первым совпадением и что его поведение стабильно (несколько вызовов на одном и том же дикторе с течением времени должны давать один и тот же элемент соответствия). Если словари не изменят свои неизмененные хэши с течением времени, так как другие элементы будут добавлены, удалены или изменены, они все равно будут работать соответствующим образом. Наивная реализация: dictObject.items(). Index (key) –

2

Существует не один, насколько я знаю, в одном случае, однако, это сделать, чтобы создать dict для обычного поиска по ключу и другого dict для обратного поиска по значению.

Там пример такой реализации здесь:

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

Это означает, что ищет ключи для значения может привести к нескольким результатам, которые могут быть возвращены в виде простого списка.

+0

Обратите внимание, что существует много, много возможных значений, которые не являются допустимыми. –

0

Через значения в словаре могут быть объекты любого типа, которые они не могут хэшировать или индексировать другим способом. Поэтому поиск ключа по значению является неестественным для этого типа коллекции. Любой такой запрос может быть выполнен только в O (n) времени. Поэтому, если это частая задача, вы должны взглянуть на некоторую индексацию ключа, например, Jon sujjested или, возможно, даже некоторый пространственный индекс (DB или http://pypi.python.org/pypi/Rtree/).

37

Есть случаи, когда словарь является один: один отображение

Например,

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

Ваш подход хорошо, если вы только делаете один поиск. Однако, если вам нужно сделать больше, чем один поиск будет более эффективным для создания инверсной словарю

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

Если есть возможность несколько ключей с тем же значением, что вам нужно будет указать желаемое поведение в этом дело.

Если Питон 2.6 или старше, вы можете использовать

ivd = dict((v, k) for k, v in d.items()) 
+6

Хорошая оптимизация. Но, я думаю, вы хотели превратить свой список 2-х кортежей в словарь с помощью dict(): 'ivd = dict ([(v, k) для (k, v) в d.items()])' – hobs

+1

Это работает, пока все значения хешируются. – askewchan

+2

@hobs просто используют понимание dict, а не понимание списка: 'invd = {v: k для k, v в d.items()}' – askewchan

59

Ваш список понимание проходит через все пункты в Словаре в находящих все матчи, то просто возвращает первый ключ. Это выражение генератор будет только итерацию по мере необходимости, чтобы вернуть первое значение:

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

где dd является ДИКТ. Поднимет StopIteration, если совпадение не найдено, поэтому вы можете поймать это и вернуть более подходящее исключение, например ValueError или KeyError.

+1

Да Должно возникнуть такое же исключение, как listObject.index (key), когда ключ отсутствует в списке. –

+5

также 'keys = {ключ для ключа, значение в dd.items(), если value == 'value'}', чтобы получить набор всех ключей, если несколько совпадений. – askewchan

+5

@askewchan - нет реальной необходимости возвращать это как набор, ключи ключей уже должны быть уникальными, просто верните список - или лучше, верните выражение генератора и позвольте вызывающему его помещать в любой контейнер, который они хотят. – PaulMcG

0

Я знаю, что это может рассматриваться как «расточительными», но в этом случае я часто хранить ключ в качестве дополнительного столбца в записи значения:

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

это компромисс и чувствует себя неправильно, но это просто и работает и, конечно, зависит от значений, а не простых значений.

+0

Предполагая, что я это сделаю, вы можете объяснить, как это поможет мне сделать обратный поиск? – Mahesh

5

Возможно, такой словарь-подобный класс, как DoubleDict ниже, что вы хотите?Вы можете использовать любой из предоставленных метаклассов в сочетании с DoubleDict или вообще избегать использования какого-либо метакласса.

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)) 
+6

OVERKILL! (Мне нравится!) – Lepi

26

Эта версия на 26% короче, чем yours но функций тождественно, даже для избыточных/неоднозначных значений (возвращает первый матч, как ваша делает). Однако он, вероятно, в два раза медленнее вашего, потому что он дважды создает список из dict.

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

Или, если вы предпочитаете краткость над читаемость вы можете сохранить еще один символ с

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

А если вы предпочитаете эффективность, @ PaulMcGuire в approach лучше. Если есть много ключей, которые разделяют то же значение, что это более эффективно не экземпляр этого списка ключей со списком понимания и вместо этого использовать использовать генератор:

key = (key for key, value in dict_obj.items() if value == 'value').next() 
+8

Это реальный ответ на вопрос. – Purrell

+0

Предполагая атомную операцию, есть ключи и значения, гарантированные в том же порядке? –

+0

@NoctisSkytower Да, 'dict.keys()' и 'dict.values ​​()' гарантированно соответствуют, если 'dict' не мутируется между вызовами. – hobs

-3
key in dict.values() 

Это буквально это

+0

инструкция непонятный. «Истина» не является ключом в оригинальном словаре. – FlipMcF

1

Нет, вы не можете сделать это эффективно, не глядя на все ключи и не проверяя все свои ценности. Для этого вам понадобится O(n) времени. Если вам нужно сделать много таких поисков, вам нужно будет сделать это эффективно, построив перевернутый словарь (можно сделать также в O(n)), а затем выполнить поиск внутри этого обратного словаря (каждый поиск будет принимать в среднем O(1)).

Вот пример того, как построить обращенную словарь (который будет в состоянии сделать один ко многим отображения) из нормального словаря:

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) 

Например, если ваш

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]) 
} 

ваш h_reversed будет

{ 
    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]) 
} 
4

Поскольку это по-прежнему актуально, первый Google ударил, и я просто потратить некоторое время выяснить это, я выложу мой (работает в Python 3) решение:

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' 

Это даст вам первое значение, которое соответствует.

0

Я использую словари как своего рода «базу данных», поэтому мне нужно найти ключ, который я могу повторно использовать. Для моего случая, если значение ключа равно None, я могу взять его и повторно использовать без необходимости «выделять» другой идентификатор. Просто подумал, что я поделюсь им.

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] 

Мне нравится этот, потому что я не имею, чтобы попытаться поймать какие-либо ошибки, такие как StopIteration или IndexError. Если есть ключ, то free_id будет содержать его. Если этого не произойдет, то это будет просто None. Наверное, не pythonic, но я действительно не хотел использовать try здесь ...

0

В стоимости может быть несуществующей в Словаре, более вещий и авто документированного код будет:

a # Value to search against 
x = None # Searched key 
for k, v in d.items(): 
    if v == a: 
     x = k 
     break 
x # Now contains the key or None if not found. 

Действительно dicts не сделали, чтобы ответить на такую ​​проблематику, если вы столкнулись с этой проблемой на новая разработанная программа, то вам, вероятно, следует рассмотреть свой дизайн.

Смежные вопросы