Python中的反向字典查找

有没有什么简单的方法通过了解字典中的值来find一个关键字?

我能想到的是这样的:

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

没有。 不要忘记,可以在任意数量的键上find该值,包括0或多于1。

你的列表理解通过所有的词典find所有的匹配项,然后只是返回第一个键。 这个生成器expression式只会根据需要迭代以返回第一个值:

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

dd是字典。 如果找不到匹配项,将会引发StopIteration ,所以您可能想要捕获并返回一个更合适的exception,如ValueErrorKeyError

有些情况下字典是一个:一个映射

例如,

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

如果您只进行一次查询,则您的方法可以。 但是,如果您需要进行多次查找,则创build反向字典会更高效

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

如果有多个具有相同值的键的可能性,则在这种情况下,您将需要指定所需的行为。

如果你的Python是2.6以上,你可以使用

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

此版本比您的版本缩短了26%,但function完全相同,即使是冗余/模糊的值(与您的版本一样返回第一个匹配)。 然而,这可能是你的两倍,因为它从字典中创build了一个列表两次。

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

或者,如果你喜欢简洁易读,你可以保存一个字符

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

如果你更喜欢效率,@ PaulMcGuire的方法更好。 如果有很多共享相同值的键,那么不用通过列表理解来实例化那些键列表就更有效率,而是使用一个生成器:

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

也许像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)) 

由于这仍然是非常相关的,第一个谷歌打了,我只是花了一些时间搞清楚这一点,我会张贴我的(在Python 3工作)解决scheme:

 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' 

它会给你第一个匹配的值。

根据我所知,没有一种方法可以做到这一点,但是要创build一个正常查找字典的键和一个反向查找字典的值。

这里有一个这样的实现的例子:

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

这意味着查找一个值的键可能会导致多个结果,可以作为一个简单的列表返回。

不,如果不查看所有的键并检查所有的值,就无法高效地完成这项工作。 所以你需要O(n)时间来做到这一点。 如果你需要做很多这样的查找,你需要通过构造一个反转的字典(也可以在O(n) ),然后在这个反转的字典中进行search, 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]) } 

通过字典中的值可以是任何types的对象,不能被其他方式散列或索引。 所以通过这个值来查找关键字对于这个集合types是不自然的。 像这样的任何查询都可以在O(n)时间内执行。 所以,如果这是频繁的任务,你应该看看像乔恩sujjested或甚至一些空间索引(DB或http://pypi.python.org/pypi/Rtree/ )的一些索引。

我知道这可能被认为是“浪费”,但在这种情况下,我经常把钥匙存储在值logging中的附加列:

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

这是一个折衷,感觉不对,但它很简单,工作,当然取决于值是元组而不是简单的值。

我使用字典作为一种“数据库”,所以我需要find一个可以重用的密钥。 对于我的情况,如果一个键的值是None ,那么我可以把它重新使用,而不必“分配”另一个id。 只是想我会分享它。

 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] 

我喜欢这个,因为我不必尝试捕获任何错误,如StopIterationIndexError 。 如果有可用的密钥,则free_id将包含一个密钥。 如果没有,那么它将只是None 。 可能不pythonic,但我真的不想在这里try

由于在字典中价值可能不存在,更多的pythonic和自动logging的代码将是:

 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. 

事实上,如果你在新devise的程序中遇到这个问题,那么你的答案就不是要回答这个问题,那么你应该回顾一下你的devise。

 key in dict.values() 

这是字面的