Python中的反向字典查找

时间:2010-04-02 19:18:28

标签: python dictionary

通过了解字典中的值,有没有直接找到密钥的方法?

我能想到的就是:

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

16 个答案:

答案 0 :(得分:76)

你的列表理解通过查找所有匹配的所有dict项目,然后只返回第一个键。此生成器表达式仅在必要时迭代以返回第一个值:

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

其中dd是dict。如果未找到匹配项,则会引发StopIteration,因此您可能希望捕获该内容并返回更合适的异常,例如ValueErrorKeyError

答案 1 :(得分:45)

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

例如,

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

答案 2 :(得分:30)

此版本比yours短26%,但功能相同,即使是冗余/模糊值(返回第一个匹配,就像你的那样)。但是,它可能比你的慢两倍,因为它会从字典中创建一个列表两次。

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

答案 3 :(得分:16)

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

答案 4 :(得分: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))

答案 5 :(得分:5)

由于这仍然非常重要,第一次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'

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

答案 6 :(得分:2)

据我所知,没有一种方法可以做到这一点,一种方法是通过键创建一个正常查找的字典,另一种方法是按值反向查找。

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

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

这确实意味着查找值的键可能会产生多个结果,这些结果可以作为简单列表返回。

答案 7 :(得分:2)

不,如果不查看所有键并检查所有值,就无法有效地执行此操作。因此,您需要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])
}

答案 8 :(得分:1)

制作反向字典

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

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

答案 9 :(得分:1)

# oneline solution using zip
>> x = {'a':100, 'b':999}
>> y = dict(zip(x.values(), x.keys()))  
>> y
{100: 'a', 999: 'b'}

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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

这是一种权衡并且感觉不对,但它很简单且有效,当然还取决于值是元组而不是简单值。

答案 12 :(得分:0)

我使用字典作为一种&#34;数据库&#34;,所以我需要找到一个可以重复使用的密钥。对于我的情况,如果一个键的值是None,那么我可以接受并重复使用它,而不必分配&#34;另一个身份。我只是想分享它。

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 ...

答案 13 :(得分:0)

由于dict中的值可能不存在,因此更加pythonic和自动记录的代码将是:

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.

事实上,如果你在一个新的设计程序中遇到这个问题,那么就不会回答这些问题,那么你应该检查一下你的设计。

答案 14 :(得分:0)

创建反向字典。然后进行常规查找。

电话簿反向查找示例:

normDic = {'KVOTHE':['789-2583']
        ,'DENNA':['987-6453']}

revDic = {'789-2583':['KVOTHE']
        ,'987-6453':['DENNA']}

numInput = str(input('Enter number:\n>>'))

if numInput in revDic:
    print('Contact found:', revDic[numInput], numInput)

答案 15 :(得分:-3)

key in dict.values()

这就是字面意思