Tra cứu từ điển nghịch đảo bằng Python


102

Có cách nào đơn giản để tìm khóa bằng cách biết giá trị trong từ điển không?

Tất cả những gì tôi có thể nghĩ là:

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



Google hướng dẫn tôi đến đây ... Và tôi phải nói .. tại sao không có ai sử dụng iteritemsnhư đối với tôi điều này làm cho một 40x nhanh hơn sự khác biệt ... bằng cách sử dụng () .next phương pháp
Angry 84

4
Nếu bạn có nhiều tra cứu ngược phải làm:reverse_dictionary = {v:k for k,v in dictionary.items()}
Austin

Câu trả lời:


5

Chẳng có ai. Đừng quên rằng giá trị có thể được tìm thấy trên bất kỳ số lượng khóa nào, bao gồm 0 hoặc nhiều hơn 1.


2
python có một phương thức .index trên danh sách trả về chỉ mục được tìm thấy đầu tiên với giá trị được chỉ định hoặc một ngoại lệ nếu không tìm thấy ... bất kỳ lý do nào tại sao một ngữ nghĩa như vậy không thể được áp dụng cho từ điển?
Brian Jack

@BrianJack: Từ điển không có thứ tự, giống như bộ. Nhìn vào collection.OrderedDict để biết một triển khai được sắp xếp theo thứ tự.
Martijn Pieters

3
.index chỉ cần đảm bảo rằng nó trả về một giá trị duy nhất và trước tiên nó không cần phải từ vựng chỉ rằng nó là kết quả phù hợp đầu tiên và hành vi của nó là ổn định (nhiều lệnh gọi trên cùng một dict theo thời gian sẽ mang lại cùng một phần tử so khớp). Trừ khi các từ điển sắp xếp lại các hàm băm chưa được sửa đổi của chúng theo thời gian khi các phần tử khác được thêm, loại bỏ hoặc sửa đổi, nó vẫn hoạt động phù hợp. Một thực hiện ngây thơ: dictObject.items chỉ số (key) ().
Brian Jack

điểm chủ yếu của .index () là theo định nghĩa chúng tôi không quan tâm đến bản sao duy nhất mà chúng tôi có thể tìm ra một yếu tố duy nhất liên tục
Brian Jack

130
Tôi ghét những câu trả lời không giống như thế này. "Đừng cố gắng làm những gì bạn muốn làm một cách chính đáng!" là không một câu trả lời có thể chấp nhận. Tại sao điều này được chấp nhận? Là câu trả lời được đánh giá cao hơn cho câu hỏi này chứng thực, tra cứu từ điển ngược có thể thực hiện được với ít hơn 80 ký tự thuần Python. Nó không nhận được bất kỳ "thẳng về phía trước" hơn thế. Giải pháp của Paul McGuire có lẽ là hiệu quả nhất, nhưng tất cả đều hoạt động. </sigh>
Cecil Curry

95

Khả năng hiểu danh sách của bạn đi qua tất cả các mục của dict tìm tất cả các mục phù hợp, sau đó chỉ cần trả về khóa đầu tiên. Biểu thức trình tạo này sẽ chỉ lặp lại chừng nào cần thiết để trả về giá trị đầu tiên:

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

chính tả ở đâu dd. Sẽ tăng StopIterationnếu không tìm thấy kết quả phù hợp nào, vì vậy bạn có thể muốn nắm bắt điều đó và trả về một ngoại lệ thích hợp hơn như ValueErrorhoặc KeyError.


1
Có Nó có thể sẽ tăng cùng một ngoại lệ như listObject.index (key) khi key không có trong danh sách.
Brian Jack

7
cũng keys = { key for key,value in dd.items() if value=='value' }để có được bộ tất cả các phím nếu một số khớp.
askewchan

6
@askewchan - thực sự không cần trả lại cái này dưới dạng một tập hợp, các khóa dict đã phải là duy nhất, chỉ cần trả về một danh sách - hoặc tốt hơn, trả về một biểu thức trình tạo và cho phép người gọi đặt nó vào bất kỳ vùng chứa nào họ muốn.
PaulMcG

55

Có những trường hợp từ điển là một: một ánh xạ

Ví dụ,

d = {1: "one", 2: "two" ...}

Cách tiếp cận của bạn là ổn nếu bạn chỉ thực hiện một lần tra cứu. Tuy nhiên, nếu bạn cần thực hiện nhiều lần tra cứu thì sẽ hiệu quả hơn khi tạo một từ điển nghịch đảo

ivd = {v: k for k, v in d.items()}

Nếu có khả năng có nhiều khóa có cùng giá trị, bạn sẽ cần chỉ định hành vi mong muốn trong trường hợp này.

Nếu Python của bạn từ 2.6 trở lên, bạn có thể sử dụng

ivd = dict((v, k) for k, v in d.items())

6
Tối ưu hóa tốt. Nhưng, tôi nghĩ bạn có nghĩa là để biến danh sách 2-tuples thành một cuốn từ điển bằng dict ():ivd=dict([(v,k) for (k,v) in d.items()])
Hobs

4
@hobs chỉ sử dụng đọc hiểu chính tả thay vì hiểu danh sách:invd = { v:k for k,v in d.items() }
askewchan

Các phần hiểu chính tả @gnibbler chưa được di chuyển trở lại Python 2.6, vì vậy nếu bạn muốn duy trì tính di động, bạn sẽ cần thêm 6 ký tự bổ sung cho dict () xung quanh trình tạo 2 bộ dữ liệu hoặc danh sách 2 bộ -tuples
hobs

@hobs, tôi đã thêm điều đó vào câu trả lời của mình.
John La Rooy

32

Phiên bản này ngắn hơn 26% so với phiên bản của bạn nhưng hoạt động giống hệt nhau, ngay cả đối với các giá trị thừa / không rõ ràng (trả về kết quả phù hợp đầu tiên, giống như của bạn). Tuy nhiên, nó có lẽ chậm gấp đôi so với của bạn, vì nó tạo ra một danh sách từ dict hai lần.

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

Hoặc nếu bạn thích sự ngắn gọn hơn là dễ đọc, bạn có thể lưu thêm một ký tự với

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

Và nếu bạn thích hiệu quả hơn, cách tiếp cận của @ PaulMcGuire sẽ tốt hơn. Nếu có nhiều khóa có cùng giá trị, hiệu quả hơn là không tạo danh sách khóa đó bằng khả năng hiểu danh sách và thay vào đó hãy sử dụng trình tạo:

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

2
Giả sử một hoạt động nguyên tử, các khóa và giá trị có được đảm bảo theo cùng một thứ tự tương ứng không?
Noctis Skytower

1
@NoctisSkytower Có, dict.keys()dict.values()được đảm bảo tương ứng miễn dictlà không bị thay đổi giữa các cuộc gọi.
hobs

7

Vì điều này vẫn còn rất phù hợp, lần đầu tiên Google tấn công và tôi chỉ dành một chút thời gian để tìm ra điều này, tôi sẽ đăng giải pháp (hoạt động trong Python 3) của mình:

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'

Nó sẽ cung cấp cho bạn giá trị đầu tiên phù hợp.


6

Có thể một lớp học giống như từ điển chẳng hạn như DoubleDictbên dưới là những gì bạn muốn? Bạn có thể sử dụng bất kỳ loại kính nào được cung cấp để kết hợp DoubleDicthoặc có thể tránh sử dụng bất kỳ loại kính nào.

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

Không, bạn không thể làm điều này một cách hiệu quả nếu không tìm kiếm tất cả các khóa và kiểm tra tất cả các giá trị của chúng. Vì vậy, bạn sẽ cần O(n)thời gian để làm điều này. Nếu bạn cần thực hiện nhiều tra cứu như vậy, bạn sẽ cần thực hiện việc này một cách hiệu quả bằng cách xây dựng một từ điển đảo ngược (cũng có thể được thực hiện trong O(n)) và sau đó thực hiện tìm kiếm bên trong từ điển đảo ngược này (trung bình mỗi lần tìm kiếm sẽ thực hiện O(1)).

Đây là một ví dụ về cách tạo một từ điển đảo ngược (có thể thực hiện một đến nhiều ánh xạ) từ một từ điển bình thường:

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)

Ví dụ nếu của bạn

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])
}

ý chí của bạn 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

Theo như tôi biết thì không có một cách nào cả, tuy nhiên có một cách để làm là tạo một dict để tra cứu thông thường theo khóa và một dict khác để tra cứu ngược theo giá trị.

Có một ví dụ về cách triển khai như vậy ở đây:

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

Điều này có nghĩa là việc tra cứu các khóa cho một giá trị có thể dẫn đến nhiều kết quả có thể được trả về dưới dạng một danh sách đơn giản.


Lưu ý rằng có rất nhiều giá trị có thể không phải là khóa hợp lệ.
Ignacio Vazquez-Abrams

1

Tôi biết điều này có thể bị coi là 'lãng phí', nhưng trong trường hợp này, tôi thường lưu trữ khóa dưới dạng một cột bổ sung trong bản ghi giá trị:

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

đó là một sự đánh đổi và cảm thấy sai, nhưng nó đơn giản và hoạt động và tất nhiên phụ thuộc vào các giá trị là các bộ giá trị thay vì các giá trị đơn giản.


1

Tạo từ điển ngược

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

Nếu bạn có nhiều tra cứu ngược phải làm


0

Thông qua các giá trị trong từ điển có thể là đối tượng của bất kỳ loại nào mà chúng không thể được băm hoặc lập chỉ mục theo cách khác. Vì vậy, việc tìm kiếm khóa theo giá trị là không tự nhiên đối với loại tập hợp này. Bất kỳ truy vấn nào như vậy chỉ có thể được thực hiện trong thời gian O (n). Vì vậy, nếu đây là nhiệm vụ thường xuyên, bạn nên xem xét một số lập chỉ mục của khóa như Jon sujjested hoặc thậm chí có thể là một số chỉ mục không gian (DB hoặc http://pypi.python.org/pypi/Rtree/ ).


-1

Tôi đang sử dụng từ điển như một loại "cơ sở dữ liệu", vì vậy tôi cần tìm một khóa mà tôi có thể sử dụng lại. Đối với trường hợp của tôi, nếu giá trị của một khóa là None, thì tôi có thể lấy nó và sử dụng lại mà không cần phải "cấp phát" một id khác. Chỉ cần tìm là tôi muốn chia sẻ nó.

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]

Tôi thích cái này vì tôi không phải thử và bắt bất kỳ lỗi nào như StopIterationhoặc IndexError. Nếu có sẵn một khóa, thì free_idsẽ chứa một khóa. Nếu không có, thì nó sẽ đơn giản là như vậy None. Có lẽ không phải pythonic, nhưng tôi thực sự không muốn sử dụng tryở đây ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.