在Python中进行逆字典查找


102

通过了解字典中的值,是否有任何简单的方法来找到密钥?

我只能想到的是:

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


看一下我的答案如何构建反向字典
Salvador Dali

Google在这里指导了我...我必须说..为什么没有iteritems人像我这样使用,这使区别快了40倍...使用().next方法
愤怒84年4

4
如果您要进行很多反向查询:reverse_dictionary = {v:k for k,v in dictionary.items()}
奥斯汀

Answers:


5

空无一人。不要忘记,可以在任意数量的键上找到该值,包括0或大于1。


2
python在列表上具有.index方法,该方法返回具有指定值的第一个找到的索引,如果未找到则返回异常...为何无法将这种语义应用于字典?
布莱恩·杰克

@BrianJack:字典不像集合一样有序。看看collections.OrderedDict为实现订购。
马丁·彼得斯

3
.index只需要保证它返回一个单一值,并且不需要在词法上仅是它是第一个匹配项,并且它的行为是稳定的(随着时间的推移对同一字典多次调用应产生相同的匹配元素)。除非字典随着时间的推移重新排列其未修改的哈希值,否则其他元素会被添加,删除或修改,否则它仍将正常工作。一个简单的实现:dictObject.items()。index(key)
Brian Jack

.index()的主要要点是,根据定义,我们不关心重复项,而只是关注可以一致地查找单个元素
Brian Jack

130
我讨厌这样的非答案。“停止尝试做您合理地想做的事!” 这不是可接受的答案。为什么这被接受?正如对该问题的较高回答所证明的那样,使用不到80个字符的纯Python即可轻松实现反向字典查找。没有比这更“直截了当”的了。保罗·麦圭尔Paul McGuire)的解决方案可能是最有效的,但它们都能奏效。</sigh>
Cecil Curry

95

您的列表理解力会遍历所有dict项,找到所有匹配项,然后仅返回第一个键。该生成器表达式将仅迭代到返回第一个值所需的程度:

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

dd字典在哪里。StopIteration如果未找到匹配项,则会加注,因此您可能想捕获该匹配项并返回更合适的异常,例如ValueErrorKeyError


1
是是当key不在列表中时,它可能会引发与listObject.index(key)相同的异常。
Brian Jack

7
keys = { key for key,value in dd.items() if value=='value' }如果有多个匹配项,还可以获取所有键的集合。
askewchan

6
@askewchan-不需要真正将其作为一个集合返回,字典键已经必须是唯一的,只需返回一个列表-或者更好的是,返回生成器表达式,然后让调用者将其放入所需的任何容器中。
PaulMcG

55

在某些情况下,字典是一对一映射

例如,

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())

6
不错的优化。但是,我认为您打算使用dict()将2元组的列表转换成字典:ivd=dict([(v,k) for (k,v) in d.items()])
滚刀

4
@hobs仅使用dict理解而不是列表理解:invd = { v:k for k,v in d.items() }
askewchan

@gnibbler dict comprehensions尚未迁移回Python 2.6,因此,如果要保持可移植性,您需要为dict()生成2个元组的生成器或列表理解为2的6个额外字符元组
滚刀

@hobs,我将其添加到了答案中。
约翰·拉鲁伊

32

这个版本比您的版本短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()

2
假设是原子操作,是否保证键和值具有相同的对应顺序?
Noctis Skytower

1
@NoctisSkytower是的,dict.keys()并且dict.values()只要它们在两次dict调用之间不发生变化,就可以保证对应。
滚刀

7

由于这仍然非常相关,因此我第一次花了点时间解决这个问题,我将发布我的(在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'

它将为您提供第一个匹配的值。


6

也许像字典这样的类(如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))

4

不,如果不查看所有键并检查所有键的值,就无法有效地做到这一点。因此,您将需要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])
}

2

据我所知,还没有一种方法,但是要做的一种方法是创建一个用于按键正常查找的字典,另一个用于按值反向查找的字典。

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

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

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


请注意,有许多可能不是有效键的值。
伊格纳西奥·巴斯克斯

1

我知道这可能被认为是“浪费的”,但是在这种情况下,我经常将键作为附加列存储在值记录中:

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

这是一个折衷方案,让人感觉错了,但是它很简单而且有效,当然取决于值是元组而不是简单的值。


1

制作反向字典

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

如果您要进行很多反向查找


0

通过字典中的值可以是任何类型的对象,它们不能以其他方式进行哈希或索引。因此,对于该集合类型,通过值查找键是不自然的。任何类似的查询只能在O(n)时间内执行。因此,如果这是一项经常性的任务,则应该查看一些像Jon sujjested这样的键的索引,或者甚至是一些空间索引(DB或http://pypi.python.org/pypi/Rtree/)。


-1

我将字典用作一种“数据库”,因此我需要找到一个可以重用的密钥。就我而言,如果键的值为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这里使用...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.