양방향 / 역지도 [중복]


101

저는 파이썬에서이 스위치 보드 작업을하고 있습니다. 여기서 누가 누구와 이야기하고 있는지 추적해야합니다. 따라서 Alice-> Bob이라면 이는 Bob-> Alice를 의미합니다.

예, 두 개의 해시 맵을 채울 수 있지만 누군가 하나로 할 아이디어가 있는지 궁금합니다.

또는 다른 데이터 구조를 제안하십시오.

여러 대화가 없습니다. 이것이 고객 서비스 콜 센터 용이라고 가정 해 보겠습니다. Alice가 스위치 보드에 전화를 걸면 그녀는 Bob 과만 대화 할 것입니다. 그의 대답은 그녀에게만 전달됩니다.


15
당신은 bijective 맵을 설명하고 있습니다.
Nick Dandoulakis

여기에 간단한 bijective Dictionary 구현 이 있지만 성능 요구 사항을 충족하는지 모르겠습니다. ( 주제에 대한 멋진 토론이있는 boost Bimap for Python대한이 블로그 기사의 링크 )
system PAUSE

2
Alice가 Bob과 이야기하고 있다면 Charles 와도 이야기 할 수 없습니다. 밥이 다른 사람과 이야기 할 수 없습니까? 또한 주어진 시간에 얼마나 많은 사람과 몇 개의 대화를 할 수 있습니까?
시스템 PAUSE

아니 ... 내 배전반이 아니야. alice가 나에게 보내는 모든 메시지는 Bob에게 가야합니다. 수천 개의 동시 대화를 라우팅 할뿐입니다. 그러나 각 사람은 한 번에 한 사람과 만 이야기합니다.
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

@TobiasKienzler : 맞습니다. 그러나 이것은 질문에 가정으로 나열되었습니다.
Sasha Chedygov

5
또한 언급 할 가치가 있습니다. 서브 클래 싱 dict은 여기에서 오해의 소지가있는 동작을 생성합니다. 초기 콘텐츠로 객체를 생성하면 구조가 깨지기 때문입니다. __init__같은 구조가 d = TwoWayDict({'foo' : 'bar'})제대로 작동하려면 재정의해야합니다 .
Henry Keiter 2014-06-04

19
이것에 대한 라이브러리가 있음을 언급하고 싶습니다 pip install bidict. URL : pypi.python.org/pypi/bidict
user1036719

45

특별한 경우에는 둘 다 하나의 사전에 저장할 수 있습니다.

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

당신이 설명하는 것은 대칭 관계이기 때문입니다. A -> B => B -> A


3
흠 ... 네,이게 제일 좋아요. 두 개의 항목을 작성하지 않으려 고했지만 지금까지는 이것이 가장 좋은 아이디어입니다.
Sudhir Jonathan

1
여전히 양방향지도가 가능해야한다고 생각합니다.-/
Sudhir Jonathan

효율적이어야한다면 해시, 정렬 된 목록, 이진 트리, 트라이, sistring으로 가득 찬 접미사 배열 또는 심지어 어떤 것이 든간에 일부 인덱스 데이터 구조에서 두 키를 모두 색인화해야합니다. 더 이국적인. Python에서이를 수행하는 간단한 방법은 해시를 사용하는 것입니다.
Kragen Javier Sitaker

@SudhirJonathan 만약 당신이 진정으로 양방향지도를 선호한다면, 예 를 들어이 질문 에서 언급 한 bidict 를 살펴보십시오 . 그러나 내 속임수 질문 에 대한 의견에서 Aya 가 논의한 성능 문제에 주목하십시오 .
Tobias Kienzler 2013 년

25

나는 그것이 오래된 질문이라는 것을 알고 있지만이 문제에 대한 또 다른 훌륭한 해결책, 즉 python package bidict 를 언급하고 싶었 습니다 . 사용하는 것은 매우 간단합니다.

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

24

두 번째 해시를 채울 것입니다.

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 2011

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
+1,의 무슨 BIDICT는 기본적으로 수행 플러스 사용하여 역 매핑을 액세스하기위한 설탕을 mydict[:value]얻기 위해 key(일부 성능의 비용)
토비아스 Kienzler

6

두 가지 문제가 있습니다.

  1. "대화"개체가 있습니다. 두 사람을 의미합니다. 한 사람이 여러 대화를 할 수 있기 때문에 다 대다 관계가 있습니다.

  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

아니요, 두 개의 사전을 만들지 않고는이를 수행 할 수있는 방법이 없습니다. 비슷한 성능을 계속 제공하면서 단 하나의 사전으로 이것을 구현하는 것이 어떻게 가능할까요?

두 개의 사전을 캡슐화하고 원하는 기능을 노출하는 사용자 지정 형식을 만드는 것이 좋습니다.


3

덜 장황한 방법이지만 여전히 reversed를 사용합니다.

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


2

또 다른 가능한 해결책은 dict원래 사전을 보유하고 역 버전을 추적 하는의 하위 클래스를 구현하는 것입니다. 키와 값이 겹치는 경우 두 개의 개별 사전을 유지하는 것이 유용 할 수 있습니다.

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

bijection 클래스 사용은 다음과 같이 쉽습니다.

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

나는 의견 중 하나에서 bidict의 제안을 좋아합니다.

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

dict다른 것들이 마음에 들지 않는 경우를 대비 하여 pythons 클래스를 확장하여 양방향 사전 구현이 하나 더 있습니다.

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)

구성을 제외하고 일반 파이썬 사전으로 사용하십시오.

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.