[python] Python에서 역 사전 조회

사전에있는 값을 알고 키를 찾는 간단한 방법이 있습니까?

내가 생각할 수있는 것은 이것뿐입니다.

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



답변

없습니다. 값은 0 또는 1보다 많은 키를 포함하여 여러 키에서 찾을 수 있음을 잊지 마십시오.


답변

목록 이해력은 모든 일치 항목을 찾는 모든 사전 항목을 살펴본 다음 첫 번째 키를 반환합니다. 이 생성기 표현식은 첫 번째 값을 반환하는 데 필요한만큼만 반복합니다.

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

dd사전은 어디에 있습니까 ? 올릴 StopIteration일치하는 항목이없는 경우 해당 잡을처럼 더 적절한 예외를 반환 할 수 있도록, ValueError또는 KeyError.


답변

사전이 일대일 매핑 인 경우가 있습니다.

예 :

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


답변

이 버전은 귀하의 버전보다 26 % 더 짧지 만 중복 / 모호한 값의 경우에도 동일하게 작동합니다 (귀하의 것과 같이 첫 번째 일치 항목을 반환 함). 그러나 dict에서 두 번 목록을 생성하기 때문에 아마도 두 배 느릴 것입니다.

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


답변

이것은 여전히 ​​매우 관련이 있기 때문에 첫 번째 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'

일치하는 첫 번째 값을 제공합니다.


답변

아마도 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))


답변

아니요, 모든 키를 확인하고 모든 값을 확인하지 않고는이 작업을 효율적으로 수행 할 수 없습니다. 따라서 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])
}