sourcetip

Python의 역사전 조회

fileupload 2023. 8. 11. 22:31
반응형

Python의 역사전 조회

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

제가 생각할 수 있는 것은 다음과 같습니다.

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

당신의 목록 이해력은 딕트의 모든 항목을 조사하여 일치하는 모든 항목을 찾은 다음 첫 번째 키를 반환합니다.이 제너레이터 식은 첫 번째 값을 반환하는 데 필요한 만큼만 반복됩니다.

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% 짧지만 중복/모호한 값에서도 동일하게 작동합니다(사용 중인 것처럼 첫 번째 일치 항목을 반환함).그러나 딕트에서 목록을 두 번 만들기 때문에 아마도 당신의 것보다 두 배 더 느릴 것입니다.

key = dict_obj.keys()[dict_obj.values().index(value)]

또는 가독성보다 간결함을 선호하는 경우 한 문자를 더 저장할 수 있습니다.

key = list(dict_obj)[dict_obj.values().index(value)]

또한 효율성을 선호하는 경우에는 @Paul McGuire의 접근 방식이 더 좋습니다.동일한 값을 공유하는 키가 많은 경우 목록 이해로 키 목록을 인스턴스화하지 않고 생성기를 사용하는 것이 더 효율적입니다.

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

이것은 여전히 매우 관련이 있기 때문에, 구글의 첫 번째 히트작이고, 저는 이것을 알아내는데 시간을 조금 소비합니다. 저의 (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))
# oneline solution using zip
>> x = {'a':100, 'b':999}
>> y = dict(zip(x.values(), x.keys()))  
>> y
{100: 'a', 999: 'b'}

아니요, 모든 키를 살펴보고 모든 값을 확인하지 않고는 이 작업을 효율적으로 수행할 수 없습니다.그래서 당신은 필요할 것입니다.O(n)할 시간입니다.이러한 검색을 많이 수행해야 할 경우 역사전을 구성하여 효율적으로 이 작업을 수행해야 합니다(에서 수행할 수도 있습니다.O(n) 이 각으로 걸립니다).O(1)).

다음은 일반 사전에서 역 사전을 구성하는 방법의 예입니다(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])
}

역방향 사전 만들기

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

수행할 역방향 조회가 많은 경우

제가 알기로는 없습니다. 하지만 한 가지 방법은 키로 정상 조회를 위한 딕트를 만들고 값으로 역 조회를 위한 딕트를 만드는 것입니다.

이러한 구현의 예는 다음과 같습니다.

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

즉, 값에 대한 키를 조회하면 여러 개의 결과를 얻을 수 있으며 이 결과는 단순 목록으로 반환될 수 있습니다.

이것이 '낭비'로 간주될 수 있다는 것을 알고 있지만, 이 시나리오에서는 종종 키를 값 레코드에 추가 열로 저장합니다.

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

그것은 트레이드오프이고 잘못 느껴지지만, 그것은 간단하고 효과적이며, 물론 단순한 가치보다는 튜플이 되는 가치에 달려 있습니다.

사전의 through 값은 다른 방식으로는 해시되거나 색인화될 수 없는 모든 종류의 개체가 될 수 있습니다.따라서 이 컬렉션 유형에서는 값으로 키를 찾는 것이 부자연스럽습니다.이와 같은 쿼리는 O(n) 시간 내에만 실행할 수 있습니다.따라서 이러한 작업이 자주 수행되는 경우 Jonsujjested와 같은 키의 인덱싱 또는 공간 인덱스(DB 또는 http://pypi.python.org/pypi/Rtree/ )를 확인해야 합니다.

저는 사전을 일종의 "데이터베이스"로 사용하고 있기 때문에 재사용할 수 있는 키를 찾아야 합니다.이 나의경이면 키값이입니다.None그러면 다른 ID를 "할당"할 필요 없이 해당 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사용가키있다면가한능,▁available▁if.free_id하나를 포함할 것입니다. 없면다은, 단히순그가 될 입니다.None아마도 비단결은 아닐 것이지만, 저는 정말로 그것을 사용하고 싶지 않았습니다.try여기...

없습니다.값은 0 또는 1 이상의 키를 포함하여 임의의 개수의 키에서 찾을 수 있습니다.

언급URL : https://stackoverflow.com/questions/2568673/inverse-dictionary-lookup-in-python

반응형