Inverse dictionary lookup in Python
有没有直接的方法通过知道字典中的值来找到一个键?
我能想到的就是:
1 | key = [key for key, value in dict_obj.items() if value == 'value'][0] |
你的列表理解通过所有的dict项找到所有匹配项,然后返回第一个键。此生成器表达式将仅在必要时迭代以返回第一个值:
1 | key = next(key for key, value in dd.items() if value == 'value') |
如果
有些情况下字典是一个:一个映射
如,
1 | d = {1:"one", 2:"two" ...} |
如果您只进行一次查找,那么您的方法是可以的。但是,如果需要进行多个查找,则创建一个反向字典会更有效。
1 | ivd = {v: k for k, v in d.items()} |
如果有可能有多个具有相同值的键,则在这种情况下需要指定所需的行为。
如果您的python是2.6或更高版本,则可以使用
1 | ivd = dict((v, k) for k, v in d.items()) |
此版本比您的版本短26%,但功能相同,即使是对于冗余/不明确的值(返回第一个匹配项,与您的匹配项相同)。但是,它的速度可能是您的慢一倍,因为它从dict中创建了两次列表。
1 | key = dict_obj.keys()[dict_obj.values().index(value)] |
或者,如果您喜欢简洁而不是易读性,您可以使用
1 | key = list(dict_obj)[dict_obj.values().index(value)] |
如果你更喜欢效率的话,@paulmcguire的方法更好。如果有许多键共享相同的值,则不通过列表理解来实例化键列表更有效,而是使用生成器:
1 | key = (key for key, value in dict_obj.items() if value == 'value').next() |
没有。不要忘记,可以在任意数量的键上找到该值,包括0或大于1。
由于这仍然是非常相关的,第一次谷歌点击,我只是花了一些时间来解决这个问题,我将发布我的(在Python3中工作)解决方案:
1 2 3 4 5 6 7 8 9 10 11 | 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' |
它将为您提供匹配的第一个值。
也许你想要一个类似字典的类,比如下面的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | 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)) |
不,如果不查找所有键并检查它们的所有值,就无法有效地执行此操作。所以你需要
下面是一个如何从普通字典构造反向字典(它将能够进行一对多映射)的示例:
1 2 3 4 5 6 | 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) |
例如,如果
1 2 3 4 5 6 7 8 9 10 | 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]) } |
你的
1 2 3 4 5 6 7 8 9 | { 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]) } |
据我所知,目前还没有一种方法,但是一种方法是创建一个按键进行正常查找的dict,另一个按值进行反向查找的dict。
这里有一个这样的实现示例:
http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/
这意味着查找某个值的键可能会产生多个结果,这些结果可以作为一个简单的列表返回。
创建反向字典。然后进行正常查找。
电话簿反向查找示例:
1 2 3 4 5 6 7 8 9 10 11 | normDic = {'KVOTHE':['789-2583'] ,'DENNA':['987-6453']} revDic = {'789-2583':['KVOTHE'] ,'987-6453':['DENNA']} numInput = str(input('Enter number: >>')) if numInput in revDic: print('Contact found:', revDic[numInput], numInput) |
由于dict中的值可能不存在,所以一个更为pythonic和自动文档化的代码将是:
1 2 3 4 5 6 7 | 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. |
事实上,口述并不是用来回答这些问题的,如果你在一个新设计的程序中遇到这个问题,那么你可能应该回顾一下你的设计。
我使用字典作为一种"数据库",所以我需要找到一个可以重用的键。在我的例子中,如果一个键的值是
1 2 3 4 5 | 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] |
我喜欢这个,因为我不必尝试去捕捉任何错误,如
我知道这可能被认为是"浪费",但在这种情况下,我经常将键作为附加列存储在值记录中:
1 | d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) } |
这是一种权衡,感觉是错误的,但它很简单,而且有效,当然取决于值是元组而不是简单值。
通过字典中的值可以是任何类型的对象,它们不能以其他方式散列或索引。因此,对于此集合类型,按值查找键是不自然的。任何类似的查询都只能在O(N)时间内执行。因此,如果这是一项频繁的任务,那么您应该寻找一些键的索引,比如jon sujjested,或者甚至一些空间索引(db或http://pypi.python.org/pypi/rtree/)。
1 | key in dict.values() |
这就是字面意思