双方向/逆マップ[複製]


101

私はpythonでこの配電盤のことをやっていて、誰が誰と話しているかを追跡する必要があるので、アリス->ボブの場合、それはボブ->アリスを意味します。

はい、2つのハッシュマップを作成できますが、誰かが1つのハッシュマップを使用することを考えているかどうか疑問に思っています。

または、別のデータ構造を提案します。

複数の会話はありません。これがカスタマーサービスのコールセンター用であるとしましょう。そのため、アリスが交換機にダイヤルすると、彼女はボブとしか話をしません。彼の返事も彼女だけに行きます。


15
全単射マップを記述していることに注意してください。
Nick Dandoulakis

これが単純な全単射辞書の実装ですが、パフォーマンス要件を満たすかどうかはわかりません。(このトピックに関するいくつかの素晴らしい議論がある、Boost Bimap for Pythonに関するこのブログ記事からのリンク。)
system PAUSE

2
アリスがボブと話しているなら、彼女がチャールズとも話していることはできないと思います。ボブは他の誰とも話をすることはできませんか?また、同時に何人の人と何回の会話ができますか?
システムの一時停止

いや...私の配電盤ではありません。アリスが私に送るメッセージはすべてボブに行く必要があります。それだけで、何千もの同時会話をルーティングします。しかし、一人一人が一度に他の一人と話すだけです。
Sudhir Jonathan

1
いいえ...顧客のメッセージをオペレーターにルーティングする必要があります(その逆も同じ)。会話を保存することすらありません。
Sudhir Jonathan、

回答:


90

dict必要なロジックをサブクラス化して追加することにより、独自の辞書タイプを作成できます。基本的な例は次のとおりです。

class TwoWayDict(dict):
    def __setitem__(self, key, value):
        # Remove any previous connections with these values
        if key in self:
            del self[key]
        if value in self:
            del self[value]
        dict.__setitem__(self, key, value)
        dict.__setitem__(self, value, key)

    def __delitem__(self, key):
        dict.__delitem__(self, self[key])
        dict.__delitem__(self, key)

    def __len__(self):
        """Returns the number of connections"""
        return dict.__len__(self) // 2

そしてそれはそのように機能します:

>>> d = TwoWayDict()
>>> d['foo'] = 'bar'
>>> d['foo']
'bar'
>>> d['bar']
'foo'
>>> len(d)
1
>>> del d['foo']
>>> d['bar']
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
KeyError: 'bar'

私はすべてのケースをカバーしなかったと確信していますが、それであなたは始められるはずです。


2
@SudhirJonathan:このアイデアをさらに進めることができます。たとえば、.addメソッドを追加して、先d.add('Bob', 'Alice')に示した構文を使用する代わりに、次のようなことができるようにします。エラー処理も含めます。しかし、あなたは基本的な考えを得ます。:)
Sasha Chedygov

1
これはそれらの追加に該当すると思いますが、新しいキーを設定するときに古いキーのペアを削除すると便利です(キーd['foo'] = 'baz'をさらに削除する必要がありbarます)。
beardc 2013年

@TobiasKienzler:あなたは正しいですが、これは質問の前提としてリストされていました。
サーシャチェディゴフ2013年

5
また、言及する価値がありdictます。ここでサブクラス化を行うと、誤解を招く動作が発生します。これは、初期コンテンツを含むオブジェクトを作成すると、構造が壊れるためです。__init__のような構造がd = TwoWayDict({'foo' : 'bar'})適切に機能するようにオーバーライドする必要があります。
Henry Keiter 2014年

19
このためのライブラリがあることをお伝えしておきますpip install bidict。URL:pypi.python.org/pypi/bidict
user1036719

45

特別なケースでは、両方を1つの辞書に保存できます。

relation = {}
relation['Alice'] = 'Bob'
relation['Bob'] = 'Alice'

あなたが説明しているのは対称的な関係だからです。 A -> B => B -> A


3
うーん...ええ、私はこれが一番好きです 2つのエントリを作成することを回避しようとしていましたが、これはこれまでのところ最高のアイデアです。
Sudhir Jonathan

1
それでも、双方向の地図が可能であるはずだと思います:-/
Sudhir Jonathan

効率的である必要がある場合、ハッシュ、ソートされたリスト、バイナリツリー、トライ、sistringでいっぱいのサフィックス配列、またはそれでも、インデックスデータ構造で両方のキーにインデックスを付ける必要がありますもっとエキゾチック。Pythonでこれを行う簡単な方法は、ハッシュを使用することです。
Kragen Javier Sitaker 2009

@SudhirJonathanあなたが本当に双方向のマップを好む場合は、見ていBIDICTをで述べた例のように、この質問 -ノートをで議論し、パフォーマンスの問題コメントで上の私だまされやすい人の質問けれども。
トビアスキエンツラー2013年

25

私はそれが古い質問であることを知っていますが、この問題の別の優れた解決策、つまりpythonパッケージbidictについて言及したいと思います。使用するのは非常に簡単です。

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])

24

私は2番目のハッシュを入力します

reverse_map = dict((reversed(item) for item in forward_map.items()))

7
そこにいくつかの余分なブラケットを手に入れました:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk

1
辞書をさらに更新しない場合の簡単な方法です。私が使用my_dict.update(dict(reversed(item) for item in my_dict.items()))
ジリ

このコードをPython 3で使用すると、警告が表示されますUnexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]])。警告を取り除く方法はありますか?
Kerwin Sneijders

9

メモリを節約できる場合、2つのハッシュマップが実際におそらく最も高速に実行されるソリューションです。私はそれらを単一のクラスにラップします-プログラマーの負担は、2つのハッシュマップが正しく同期するようにすることです。


2
+1、それはbidictが基本的に行うことであり、さらに(パフォーマンスを犠牲にして)を使用mydict[:value]して逆マッピングにアクセスするための砂糖key
Tobias Kienzler 2013年

6

2つの別々の問題があります。

  1. 「会話」オブジェクトがあります。それは2人を指します。Personは複数の会話を持つことができるため、多対多の関係があります。

  2. 個人から会話のリストへのマップがあります。変換には一対の人がいます。

このようなことをする

from collections import defaultdict
switchboard= defaultdict( list )

x = Conversation( "Alice", "Bob" )
y = Conversation( "Alice", "Charlie" )

for c in ( x, y ):
    switchboard[c.p1].append( c )
    switchboard[c.p2].append( c )

5

いいえ、2つの辞書を作成せずにこれを行う方法はありません。同等のパフォーマンスを提供し続けながら、1つの辞書だけでこれを実装することはどのように可能ですか?

2つのディクショナリをカプセル化し、必要な機能を公開するカスタムタイプを作成した方がよいでしょう。


3

あまり冗長ではない方法ですが、まだ逆を使用しています:

dict(map(reversed, my_dict.items()))


2

別の可能な解決策は、のサブクラスを実装するdictことです。これは、元の辞書を保持し、その逆バージョンを追跡します。キーと値が重複している場合は、2つの別々のディクテーションを維持すると便利です。

class TwoWayDict(dict):
    def __init__(self, my_dict):
        dict.__init__(self, my_dict)
        self.rev_dict = {v : k for k,v in my_dict.iteritems()}

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        self.rev_dict.__setitem__(value, key)

    def pop(self, key):
        self.rev_dict.pop(self[key])
        dict.pop(self, key)

    # The above is just an idea other methods
    # should also be overridden. 

例:

>>> d = {'a' : 1, 'b' : 2} # suppose we need to use d and its reversed version
>>> twd = TwoWayDict(d)    # create a two-way dict
>>> twd
{'a': 1, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b'}
>>> twd['a']
1
>>> twd.rev_dict[2]
'b'
>>> twd['c'] = 3    # we add to twd and reversed version also changes
>>> twd
{'a': 1, 'c': 3, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b', 3: 'c'}
>>> twd.pop('a')   # we pop elements from twd and reversed  version changes
>>> twd
{'c': 3, 'b': 2}
>>> twd.rev_dict
{2: 'b', 3: 'c'}

2

pypiにはcollections-extendedライブラリがあります:https ://pypi.python.org/pypi/collections-extended/0.6.0

バイジェクションクラスの使用は次のように簡単です。

RESPONSE_TYPES = bijection({
    0x03 : 'module_info',
    0x09 : 'network_status_response',
    0x10 : 'trust_center_device_update'
})
>>> RESPONSE_TYPES[0x03]
'module_info'
>>> RESPONSE_TYPES.inverse['network_status_response']
0x09

2

私はコメントの1つでバイディクトの提案を気に入っています。

pip install bidict

使用法:

# This normalization method should save hugely as aDaD ~ yXyX have the same form of smallest grammar.
# To get back to your grammar's alphabet use trans

def normalize_string(s, nv=None):
    if nv is None:
        nv = ord('a')
    trans = bidict()
    r = ''
    for c in s:
        if c not in trans.inverse:
            a = chr(nv)
            nv += 1
            trans[a] = c
        else:
            a = trans.inverse[c]
        r += a
    return r, trans


def translate_string(s, trans):
    res = ''
    for c in s:
        res += trans[c]
    return res


if __name__ == "__main__":
    s = "bnhnbiodfjos"

    n, tr = normalize_string(s)
    print(n)
    print(tr)
    print(translate_string(n, tr))    

それについてのドキュメントはあまりないので。しかし、私はそれが正しく機能するために必要なすべての機能を持っています。

プリント:

abcbadefghei
bidict({'a': 'b', 'b': 'n', 'c': 'h', 'd': 'i', 'e': 'o', 'f': 'd', 'g': 'f', 'h': 'j', 'i': 's'})
bnhnbiodfjos

1

kjbuckets C拡張モジュールは、「グラフ」データ構造を提供します。これは、あなたが望むものを提供すると私は信じています。


申し訳ありませんが、言及しませんでしたが、それはApp Engine上にあります。
Sudhir Jonathan、

1

dict他のものが好きではない場合に備えて、pythons クラスを拡張したもう1つの双方向辞書の実装を次に示します。

class DoubleD(dict):
    """ Access and delete dictionary elements by key or value. """ 

    def __getitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            return inv_dict[key]
        return dict.__getitem__(self, key)

    def __delitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            dict.__delitem__(self, inv_dict[key])
        else:
            dict.__delitem__(self, key)

構築時以外は通常のpython辞書として使用します。

dd = DoubleD()
dd['foo'] = 'bar'

1

この種のことをするのが好きな方法は次のようなものです:

{my_dict[key]: key for key in my_dict.keys()}

1
StackOverflowへようこそ!してくださいあなたの答えを編集し、あなたのコードのためのより多くの説明を追加し、また、好ましくは、それは14の他の答えは異なるだ理由として説明を加えます。質問は10年以上前のものであり、すでに受け入れられた回答と、よく説明され、よく支持された多くの回答があります。あなたの投稿に詳細がなければ、それは比較的低品質であり、おそらく反対投票または削除されます。その追加情報を追加すると、回答の存在を正当化するのに役立ちます。
Das_Geek
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.