Python 中的逆字典查找
- 2025-02-10 08:57:00
- admin 原创
- 49
问题描述:
有没有直接的方法可以通过了解字典中的值来找到键?
我能想到的只有这个:
key = [key for key, value in dict_obj.items() if value == 'value'][0]
解决方案 1:
列表推导会遍历字典中的所有项,找到所有匹配项,然后返回第一个键。此生成器表达式只会在需要时进行迭代以返回第一个值:
key = next(key for key, value in dd.items() if value == 'value')
dd
字典在哪里。StopIteration
如果未找到匹配项,则会引发,因此您可能希望捕获该异常并返回更合适的异常,例如ValueError
或KeyError
。
解决方案 2:
在某些情况下,字典是一对一映射
例如,
d = {1: "one", 2: "two" ...}
如果您只进行一次查找,您的方法没问题。但是,如果您需要进行多次查找,创建逆向词典会更有效率
ivd = {v: k for k, v in d.items()}
如果有可能多个键具有相同的值,则需要在这种情况下指定所需的行为。
如果你的 Python 版本是 2.6 或更早版本,你可以使用
ivd = dict((v, k) for k, v in d.items())
解决方案 3:
此版本比您的版本短 26% ,但功能相同,即使对于冗余/模糊值也是如此(返回第一个匹配项,就像您的版本一样)。但是,它的速度可能比您的版本慢两倍,因为它从字典中创建了两次列表。
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()
解决方案 4:
由于这仍然非常相关,第一个谷歌命中,我只是花了一些时间来解决这个问题,我将发布我的(使用 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'
它将为您提供第一个匹配的值。
解决方案 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 != \n 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') \n 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') \n 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:
制作反向词典
reverse_dictionary = {v:k for k,v in dictionary.items()}
如果你有很多反向查找要做
解决方案 7:
# oneline solution using zip
>> x = {'a':100, 'b':999}
>> y = dict(zip(x.values(), x.keys()))
>> y
{100: 'a', 999: 'b'}
解决方案 8:
不,如果不查看所有键并检查所有值,您就无法有效地完成此操作。因此,您需要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])
}
解决方案 9:
据我所知,没有这样的方法,但是有一种方法是创建一个字典用于按键的正常查找,并创建另一个字典用于按值进行反向查找。
这里有一个这种实现的示例:
http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/
这确实意味着查找某个值的键可能会产生多个结果,这些结果可以作为简单列表返回。
解决方案 10:
我知道这可能被认为是“浪费”,但在这种情况下,我经常将键存储为值记录中的附加列:
d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) }
这是一种权衡并且感觉不对,但它很简单并且有效,当然取决于值是元组而不是简单值。
解决方案 11:
虽然字典中的值可以是任何类型的对象,但它们不能以其他方式进行哈希处理或索引。因此,对于这种集合类型,通过值查找键是不自然的。任何这样的查询都只能在 O(n) 时间内执行。因此,如果这是一项频繁的任务,您应该查看一些键索引,如 Jon sujjested,或者甚至是一些空间索引(DB 或http://pypi.python.org/pypi/Rtree/)。
解决方案 12:
我将字典用作一种“数据库”,因此我需要找到一个可以重复使用的键。就我的情况而言,如果键的值是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]
我喜欢这个,因为我不必尝试捕获任何错误,例如StopIteration
或IndexError
。如果有可用的键,则将free_id
包含一个。如果没有,则它将只是None
。可能不是 Python 风格,但我真的不想try
在这里使用...
解决方案 13:
没有。请不要忘记,该值可能出现在任意数量的键上,包括 0 个或 1 个以上。