Python에서 역 사전 조회


102

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

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

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



Google이 저를 여기로 안내했습니다 ... 그리고 제가 말해야합니다 .. 왜 아무도 iteritems저를 사용하지 않는지 이게 40 배 더 빠른 차이를 만듭니다 ... () .next 메서드를 사용하여
Angry 84

4
역방향 조회가 많은 경우 :reverse_dictionary = {v:k for k,v in dictionary.items()}
Austin

답변:


5

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


2
파이썬은 목록에 .index 메소드가 있으며 지정된 값을 가진 첫 번째 발견 된 인덱스를 반환하거나 찾을 수없는 경우 예외를 반환합니다 ... 이러한 의미를 사전에 적용 할 수없는 이유는 무엇입니까?
Brian Jack

@BrianJack : 사전은 세트처럼 정렬되지 않습니다. 순서 지정된 구현에 대해서는 collections.OrderedDict를보십시오 .
Martijn Pieters

3
.index는 단일 값을 반환하도록 보장하기 만하면되며 첫 번째 일치 항목이고 동작이 안정적인지만 어휘 적으로 먼저 수행 할 필요는 없습니다 (시간이 지남에 따라 동일한 dict에 대한 여러 호출이 동일한 일치 요소를 생성해야 함). 다른 요소가 추가, 제거 또는 수정됨에 따라 사전이 수정되지 않은 해시를 시간이 지남에 따라 재 배열하지 않는 한 여전히 적절하게 작동합니다. 순진한 구현 : dictObject.items (). index (key)
Brian Jack

주로있는 .index ()의 점은 정의에 의해 우리는 우리가 일관되게 하나의 요소 찾아 볼 수 있습니다 것만 중복 걱정하지 않는다는 것입니다
브라이언 잭

130
나는 이런 대답이없는 것을 몹시 싫어한다. "당연히하고 싶은 일을하지 마세요!" 허용되는 대답 이 아닙니다 . 이것이 왜 받아 들여졌습니까? 이 질문에 대한 높은 등급의 답변이 증명 하듯이 역 사전 조회는 순수 Python의 80 자 미만으로 사소하게 구현할 수 있습니다. 그것은 그것보다 더 "직진 적"을 얻지 못합니다. Paul McGuire솔루션 이 아마도 가장 효율적일 수 있지만 모두 작동합니다. </sigh>
Cecil Curry

95

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

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

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


1
예 키가 목록에없는 경우 listObject.index (key)와 동일한 예외가 발생해야합니다.
Brian Jack

7
또한 keys = { key for key,value in dd.items() if value=='value' }여러 일치하는 경우 모든 키 집합을 가져옵니다.
askewchan 2013 년

6
@askewchan-이것을 세트로 반환 할 필요가 없습니다. dict 키는 이미 고유해야합니다. 목록을 반환하거나 더 나은 방법으로 생성기 표현식을 반환하고 호출자가 원하는 컨테이너에 넣도록합니다.
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()])
hobs

4
@hobs는 목록 이해 대신 dict 이해를 사용합니다.invd = { v:k for k,v in d.items() }
askewchan

@gnibbler dict comprehensions는 Python 2.6으로 다시 마이그레이션되지 않았으므로 이식성을 유지하려면 2 튜플 생성기 또는 2의 목록 이해력 주위에 dict ()에 대한 6 개의 추가 문자를 입력해야합니다. -tuples
hobs

@hobs, 내 대답에 추가했습니다.
John La Rooy

32

이 버전은 귀하의 버전보다 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()

2
원자 적 연산을 가정하면 키와 값이 동일한 순서로 보장됩니까?
Noctis Skytower

1
@NoctisSkytower 예, dict.keys()그리고 dict.values()는만큼 해당하는 보장 dict호출 사이에 변이되지 않습니다.
hobs

7

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

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

내가 아는 한 하나도 없지만 한 가지 방법은 키로 정상적인 조회를위한 dict를 만들고 값으로 역방향 조회를위한 또 다른 dict를 만드는 것입니다.

여기에 이러한 구현의 예가 있습니다.

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

이는 값에 대한 키를 조회하면 간단한 목록으로 반환 될 수있는 여러 결과가 발생할 수 있음을 의미합니다.


유효한 키가 아닌 가능한 값이 많이 있습니다.
Ignacio Vazquez-Abrams

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]

나는 StopIteration또는 같은 오류를 시도하고 잡을 필요가 없기 때문에 이것을 좋아합니다 IndexError. 사용 가능한 키가있는 경우 키 free_id가 포함됩니다. 없는 경우 단순히 None. 아마 비단뱀 같지는 않지만 try여기 에서 사용하고 싶지 않았습니다 ...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.