Python'da ters sözlük araması


103

Bir sözlükteki değeri bilerek bir anahtar bulmanın doğrudan bir yolu var mı?

Tek düşünebildiğim şu:

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



Google bana burada rehberlik etti ... Ve söylemeliyim ki .. neden kimse iteritemsbenim için kullanmıyor , bu 40 kat daha hızlı bir fark yaratıyor ... () .sonraki yöntemi kullanarak
Angry 84

4
Yapacak çok fazla ters aramanız varsa:reverse_dictionary = {v:k for k,v in dictionary.items()}
Austin

Yanıtlar:


4

Hiçbiri yok. Değerin 0 veya 1'den fazla dahil olmak üzere herhangi bir sayıda anahtar üzerinde bulunabileceğini unutmayın.


2
python, listeler üzerinde bir .index yöntemine sahiptir, belirtilen değere sahip ilk bulunan dizini döndürür veya bulunamazsa bir istisna ... böyle bir anlambilimin sözlüklere uygulanamamasının herhangi bir nedeni var mı?
Brian Jack

@BrianJack: Sözlükler, setler gibi sıralanmaz. Bir uygulama için collections.OrderedDict bak edilir emretti.
Martijn Pieters

3
.index'in yalnızca tek bir değer döndürdüğünü garanti etmesi gerekir ve yalnızca ilk eşleşme olması ve davranışının kararlı olması için sözcüksel olarak ilk olması gerekmez (zaman içinde aynı diktede birden çok çağrı aynı eşleşen öğeyi vermelidir). Sözlükler, diğer öğeler eklendikçe, kaldırıldıkça veya değiştirildikçe zaman içinde değiştirilmemiş karmalarını yeniden düzenlemedikçe, yine de uygun şekilde çalışacaktır. Saf bir uygulama: dictObject.items (). İndex (anahtar)
Brian Jack

Esas olarak .index () 'in ana noktası, tanım gereği kopyaları umursamadığımızdır, yalnızca tek bir öğeyi tutarlı bir şekilde arayabiliriz
Brian Jack

131
Böyle cevapsızlıktan nefret ediyorum. "Haklı olarak yapmak istediğini yapmaya çalışmayı bırak!" olduğu olmayan kabul edilebilir bir cevap. Bu neden kabul edildi? Bu soruya verilen daha yüksek puanlı yanıtların da kanıtladığı gibi, ters sözlük araması, saf Python'un 80'den az karakterinde önemsiz bir şekilde uygulanabilir. Bundan daha "düz ileri" olamaz. Paul McGuire 'ın çözüm en verimli muhtemelen, ama hepsi çalışır. </sigh>
Cecil Curry

96

Listenizi anlamanız, tüm eşleşmeleri bulan tüm emir öğelerini gözden geçirir, ardından yalnızca ilk anahtarı döndürür. Bu oluşturucu ifadesi, yalnızca ilk değeri döndürmek için gerektiği kadar yinelenecektir:

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

dddikte nerede . Eşleşme StopIterationbulunmazsa yükselir, bu yüzden onu yakalamak ve ValueErrorveya gibi daha uygun bir istisna döndürmek isteyebilirsiniz KeyError.


1
Evet Anahtar listede olmadığında muhtemelen listObject.index (anahtar) ile aynı istisnayı yükseltmelidir.
Brian Jack

7
ayrıca keys = { key for key,value in dd.items() if value=='value' }birkaç eşleşme varsa tüm anahtarların setini almak için.
askewchan

6
@askewchan - bunu bir küme olarak döndürmeye gerçekten gerek yok, dikt anahtarlarının zaten benzersiz olması gerekir, sadece bir liste döndürür - veya daha iyisi, bir jeneratör ifadesi döndürür ve arayanın istediği konteynere koymasına izin verir.
PaulMcG

57

Bir sözlüğün tek olduğu durumlar vardır: tek eşleme

Örneğin,

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

Yalnızca tek bir arama yapıyorsanız yaklaşımınız uygundur. Bununla birlikte, birden fazla arama yapmanız gerekiyorsa, ters bir sözlük oluşturmak daha verimli olacaktır.

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

Aynı değere sahip birden fazla anahtar olasılığı varsa, bu durumda istenen davranışı belirlemeniz gerekecektir.

Python 2.6 veya daha eskiyse, şunu kullanabilirsin:

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

6
Güzel optimizasyon. Ancak, 2-tuple listenizi dict () kullanarak bir sözlüğe dönüştürmek istediğinizi düşünüyorum:ivd=dict([(v,k) for (k,v) in d.items()])
hobs

4
@hobs, liste anlama yerine sadece bir dikte anlama kullanıyor:invd = { v:k for k,v in d.items() }
askewchan

@gnibbler dict anlayışları Python 2.6'ya geri taşınmadı, bu yüzden taşınabilir kalmak istiyorsanız, 2-tuple oluşturucu veya 2'li bir liste anlayışı etrafında dict () için 6 ekstra karakter koymanız gerekir. -tuples
ocaklar

@hobs, bunu cevabıma ekledim.
John La Rooy

32

Bu sürüm sizinkinden % 26 daha kısadır ancak gereksiz / belirsiz değerler için bile aynı şekilde çalışır (sizinki gibi ilk eşleşmeyi döndürür). Ancak, muhtemelen sizinkinden iki kat daha yavaştır, çünkü iki kez dikteden bir liste oluşturur.

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

Ya da kısalığı okunabilirliğe tercih ederseniz, bir karakter daha kaydedebilirsiniz.

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

Verimliliği tercih ederseniz, @ PaulMcGuire'ın yaklaşımı daha iyidir. Aynı değeri paylaşan çok sayıda anahtar varsa, bu anahtar listesini bir liste anlayışıyla başlatmamak ve bunun yerine bir oluşturucu kullanmak daha etkilidir:

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

2
Atomik bir işlem varsayarsak, anahtarların ve değerlerin aynı sırayla olması garanti ediliyor mu?
Noctis Skytower

1
@NoctisSkytower Evet dict.keys()ve aramalar arasında mutasyona uğramadığı dict.values()sürece karşılık geleceği garantilidir dict.
ocak

7

Bu hala çok alakalı olduğu için, ilk Google isabeti ve bunu çözmek için biraz zaman harcıyorum, (Python 3'te çalışma) çözümümü göndereceğim:

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'

Size eşleşen ilk değeri verecektir.


6

DoubleDictAşağıdakiler gibi sözlüğe benzeyen bir sınıf olabilir mi? Sağlanan metasınıflardan herhangi birini herhangi bir metasınıf ile birlikte kullanabilir DoubleDictveya kullanmaktan kaçınabilirsiniz.

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

Hayır, tüm anahtarlara bakmadan ve tüm değerlerini kontrol etmeden bunu verimli bir şekilde yapamazsınız. Yani bunu O(n)yapmak için zamana ihtiyacın olacak . Bu tür çok sayıda arama yapmanız gerekiyorsa, bunu tersine çevrilmiş bir sözlük oluşturarak (içinde de yapılabilir O(n)) ve ardından bu ters çevrilmiş sözlüğün içinde bir arama yaparak (her arama ortalama olarakO(1) ) .

Normal bir sözlükten tersine çevrilmiş bir sözlüğün nasıl oluşturulacağına dair bir örnek (birden çoğa eşleme yapabilecek):

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)

Örneğin

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

senin h_reversedolacaksın

{
  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

Bildiğim kadarıyla bir tane yok, ancak bunu yapmanın bir yolu, anahtara göre normal arama için bir dikte ve değere göre ters arama için başka bir dikt oluşturmaktır.

Burada böyle bir uygulamanın bir örneği var:

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

Bu, bir değer için anahtarların aranmasının, basit bir liste olarak döndürülebilecek birden çok sonuçla sonuçlanabileceği anlamına gelir.


Geçerli anahtarlar olmayan pek çok olası değer olduğunu unutmayın.
Ignacio Vazquez-Abrams

1

Bunun 'savurgan' olarak değerlendirilebileceğini biliyorum, ancak bu senaryoda anahtarı genellikle değer kaydında ek bir sütun olarak saklıyorum:

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

bu bir değiş tokuş ve yanlış hissediyor, ancak basit ve işe yarıyor ve elbette değerlerin basit değerlerden ziyade tekil olmasına bağlı.


1

Ters sözlük yapın

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

Yapacak çok fazla ters aramanız varsa


Bu, yalnızca anahtarlar ve değerler arasında 1: 1 eşleme olduğunda çalışır.
Noel Yap

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

0

Sözlükteki değerler aracılığıyla herhangi bir nesne olabilir, hashing uygulanamaz veya başka şekilde dizine eklenemez. Bu nedenle, değere göre anahtarı bulmak bu koleksiyon türü için doğal değildir. Bunun gibi herhangi bir sorgu yalnızca O (n) zamanında yürütülebilir. Dolayısıyla, bu sık sık yapılan bir görevse, Jon sujjested gibi bir anahtar indekslemesine veya hatta bazı uzamsal indekslere (DB veya http://pypi.python.org/pypi/Rtree/ ) göz atmalısınız .


-1

Sözlükleri bir tür "veritabanı" olarak kullanıyorum, bu nedenle yeniden kullanabileceğim bir anahtar bulmam gerekiyor. Benim durumum için, bir anahtarın değeri ise None, o zaman onu alıp başka bir kimliği "tahsis etmek" zorunda kalmadan yeniden kullanabilirim. Paylaşacağımı düşündüm.

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]

Bunu beğendim çünkü StopIterationveya gibi herhangi bir hatayı yakalamam gerekmiyor IndexError. Kullanılabilir bir anahtar varsa, o free_idzaman bir tane içerir. Eğer yoksa, o zaman basitçe olacaktır None. Muhtemelen pitonik değil, ama gerçekten tryburada kullanmak istemedim ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.