Pythonでの逆辞書検索


102

辞書内の値を知ることによってキーを見つける簡単な方法はありますか?

私が考えることができるすべてはこれです:

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



グーグルはここで私を導きました...そして私は言わなければなりません..なぜiteritems私のために誰も使用していないので、これは40倍速い違いになります...().nextメソッドを使用して
Angry 84

4
行うべき逆ルックアップがたくさんある場合:reverse_dictionary = {v:k for k,v in dictionary.items()}
オースティン

回答:


5

なにもない。値は、0または1を超える任意の数のキーにある可能性があることを忘れないでください。


2
pythonはリストに.indexメソッドを持っています。指定された値で最初に見つかったインデックスを返します。見つからない場合は例外を返します。そのようなセマンティクスを辞書に適用できない理由はありますか?
ブライアンジャック

@BrianJack:辞書はセットのように順序付けされていません。順序付けされた実装について、collections.OrderedDictを参照してください。
Martijn Pieters

3
.indexは、それが単一の値を返すことを保証する必要があるだけであり、それが最初に一致することと、その動作が安定していることだけが語彙的に最初である必要はありません(同じディクショナリでの複数の呼び出しは、同じ一致する要素を生成するはずです)。辞書が他の要素が追加、削除、または変更されたときに、変更されていないハッシュを時間の経過とともに再配置しない限り、適切に機能します。素朴な実装:dictObject.items()。index(key)
ブライアンジャック

主に.index()の要点は、定義では重複を気にしないということです。これは、単一の要素を一貫して検索できるということだけです
Brian Jack

130
私はこのような非回答を嫌います。「あなたが正当にやりたいことをやろうとするのをやめなさい!」許容できる答えではありません。なぜこれが受け入れられたのですか?この質問に対するより高い評価の回答が証明しているように、逆辞書ルックアップは80文字未満の純粋なPythonで簡単に実装できます。それよりも「まっすぐ進む」ことはありません。Paul McGuireソリューションがおそらく最も効率的ですが、すべて機能します。</sigh>
セシルカレー

95

リスト内包表記は、すべての辞書の項目を調べてすべての一致を見つけ、最初のキーを返します。このジェネレータ式は、最初の値を返すために必要なだけ反復します。

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

dddictはどこにありますか。引き上げるStopIteration一致が見つからない場合は、あなたがいることをキャッチし、同様に、より適切な例外を返すようにしたいかもしれませんので、ValueErrorまたはKeyError


1
はいkeyがリストにない場合は、おそらくlistObject.index(key)と同じ例外が発生するはずです。
ブライアンジャック

7
またkeys = { key for key,value in dd.items() if value=='value' }、いくつかが一致した場合にすべてのキーのセットを取得します。
askewchan 2013年

6
@askewchan-これをセットとして返す必要はありません。dictキーは既に一意である必要があります。リストを返すだけです。より良いのは、ジェネレーター式を返し、呼び出し側がそれを任意のコンテナーに配置できるようにすることです。
PaulMcG 2013年

55

辞書が1対1のマッピングである場合があります

例えば、

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

ルックアップを1回だけ行う場合は、このアプローチで問題ありません。ただし、複数のルックアップを実行する必要がある場合は、逆辞書を作成する方が効率的です。

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()])
コンロ

4
@hobsだけではなく、リスト内包の辞書の理解を使用しますinvd = { v:k for k,v in d.items() }
askewchan

@gnibbler dict内包表記はPython 2.6に移行されていないため、移植性を維持したい場合は、2タプルのジェネレーターまたはリスト内包表記2の周りのdict()に6文字追加する必要があります。
ホブ

@hobs、私はそれを私の答えに追加しました。
John La Rooy、2013年

32

このバージョンは、あなたのバージョンより26%短いですが、冗長/あいまいな値でも同じように機能します(あなたのように最初の一致を返します)。ただし、dictから2度リストを作成するため、おそらく2倍遅くなります。

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スカイタワー2016年

1
@NoctisSkytowerはい、dict.keys()そしてdict.values()限り対応するように保証されているdictコールの間に変異されていません。
ホブ、2016年

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

通常の辞書から逆引き辞書(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

私が知る限りこれはありませんが、そのための1つの方法は、キーによる通常の検索用の辞書と、値による逆検索用の辞書を作成することです。

ここにそのような実装の例があります:

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

それはトレードオフであり、間違っていると感じますが、それは単純で機能し、もちろん、単純な値ではなくタプルである値に依存します。



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]

このような私は、私がしようとするなどのすべてのエラーをキャッチする必要がないためStopIterationIndexError。利用可能なキーがある場合は、キーfree_idが含まれます。ない場合は、単純にになりますNone。おそらくpythonicではないが、私は本当にtryここを使いたくなかった...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.