Bản đồ hai chiều / ngược lại [trùng lặp]


101

Tôi đang làm điều này trong tổng đài bằng python, nơi tôi cần theo dõi ai đang nói chuyện với ai, vì vậy nếu Alice -> Bob, thì điều đó ngụ ý rằng Bob -> Alice.

Có, tôi có thể điền vào hai bản đồ băm, nhưng tôi đang tự hỏi liệu có ai có ý tưởng làm điều đó với một bản đồ không.

Hoặc đề xuất cấu trúc dữ liệu khác.

Không có nhiều cuộc trò chuyện. Giả sử đây là cho một trung tâm cuộc gọi dịch vụ khách hàng, vì vậy khi Alice gọi vào tổng đài, cô ấy sẽ chỉ nói chuyện với Bob. Những câu trả lời của anh cũng chỉ dành cho cô.


15
lưu ý rằng bạn đang mô tả một bản đồ sinh học.
Nick Dandoulakis 21/09/09

Đây là cách triển khai Từ điển bijective đơn giản , mặc dù tôi không biết liệu nó có đáp ứng được các yêu cầu về hiệu suất của bạn hay không. (Liên kết từ bài viết blog này về boost Bimap cho Python , có một số cuộc thảo luận hay về chủ đề.)
system PAUSE 21/09/09

2
Nếu Alice đang nói chuyện với Bob, tôi cho rằng cô ấy cũng không thể nói chuyện với Charles; Bob cũng không được nói chuyện với ai khác? Ngoài ra, bạn có thể có bao nhiêu người và bao nhiêu cuộc trò chuyện tại một thời điểm nhất định?
hệ thống TẠM DỪNG

Không ... không có trên tổng đài của tôi. Bất kỳ tin nhắn nào mà alice gửi cho tôi sẽ phải đến tay Bob. Tôi chỉ định tuyến hàng nghìn cuộc trò chuyện đồng thời. Nhưng mỗi người chỉ nói chuyện với một người khác tại một thời điểm.
Sudhir Jonathan

1
Không ... tôi chỉ cần định tuyến tin nhắn của khách hàng đến nhà điều hành và ngược lại ... thậm chí không cần lưu trữ các cuộc trò chuyện theo bất kỳ cách nào.
Sudhir Jonathan

Câu trả lời:


90

Bạn có thể tạo loại từ điển của riêng mình bằng cách phân lớp con dict và thêm logic mà bạn muốn. Đây là một ví dụ cơ bản:

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

Và nó hoạt động như vậy:

>>> 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'

Tôi chắc chắn rằng tôi đã không bao gồm tất cả các trường hợp, nhưng điều đó sẽ giúp bạn bắt đầu.


2
@SudhirJonathan: Bạn có thể tiến xa hơn với ý tưởng này - ví dụ: thêm một .addphương thức để bạn có thể làm những việc như d.add('Bob', 'Alice')thay vì sử dụng cú pháp mà tôi đã trình bày. Tôi cũng sẽ bao gồm một số xử lý lỗi. Nhưng bạn có được ý tưởng cơ bản. :)
Sasha Chedygov

1
Tôi đoán điều này nằm trong những bổ sung đó, nhưng sẽ hữu ích nếu xóa các cặp khóa cũ khi thiết lập các cặp khóa mới ( d['foo'] = 'baz'sẽ cần phải xóa thêm barkhóa).
râuc

@TobiasKienzler: Bạn nói đúng, nhưng đây được liệt kê là một giả định trong câu hỏi.
Sasha Chedygov

5
Cũng đáng đề cập: phân lớp dicttạo ra một số hành vi gây hiểu lầm ở đây, bởi vì nếu bạn tạo đối tượng với một số nội dung ban đầu, cấu trúc sẽ bị phá vỡ. __init__cần được ghi đè để cho phép một công trình xây dựng d = TwoWayDict({'foo' : 'bar'})hoạt động bình thường.
Henry Keiter

19
Chỉ muốn đề cập đến rằng có một thư viện cho việc này: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719 Ngày

45

Trong trường hợp đặc biệt của bạn, bạn có thể lưu trữ cả hai trong một từ điển:

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

Vì những gì bạn đang mô tả là một mối quan hệ đối xứng. A -> B => B -> A


3
Hmm ... vâng, tôi thích cái này nhất. Đã cố gắng tránh thực hiện hai mục, nhưng đây là ý tưởng tốt nhất cho đến nay.
Sudhir Jonathan

1
Vẫn nghĩ rằng nên có bản đồ hai chiều: - /
Sudhir Jonathan

Nếu nó phải hiệu quả, thì bạn cần có cả hai khóa để được lập chỉ mục trong một số cấu trúc dữ liệu chỉ mục - cho dù đó là hàm băm, danh sách được sắp xếp, cây nhị phân, một bộ ba, một mảng hậu tố đầy đủ các cung hoặc một cái gì đó thậm chí kỳ lạ hơn. Cách đơn giản để làm điều đó trong Python là sử dụng một hàm băm.
Kragen Javier Sitaker 22/09/09

@SudhirJonathan Nếu bạn thích một bản đồ hai chiều thực sự, hãy xem bidict như đã đề cập, ví dụ như trong câu hỏi này - hãy lưu ý các vấn đề về hiệu suất được Aya thảo luận trong phần nhận xét về câu hỏi dupe của tôi .
Tobias Kienzler

25

Tôi biết đó là một câu hỏi cũ, nhưng tôi muốn đề cập đến một giải pháp tuyệt vời cho vấn đề này, cụ thể là gói python bidict . Nó cực kỳ dễ sử dụng:

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

24

Tôi sẽ chỉ điền một băm thứ hai, với

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

7
Có một số dấu ngoặc thêm trong đó:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk

1
Rất dễ dàng nếu bạn không cập nhật thêm bài đọc. Tôi đã sử dụngmy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

Khi sử dụng mã này trong Python 3 tôi nhận được một cảnh báo: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Bất kỳ ý tưởng làm thế nào để thoát khỏi cảnh báo?
Kerwin Sneijders

9

Hai bản đồ băm thực sự có lẽ là giải pháp hoạt động nhanh nhất giả sử bạn có thể dành bộ nhớ. Tôi sẽ gói chúng trong một lớp duy nhất - gánh nặng đối với lập trình viên là đảm bảo rằng hai bản đồ băm đồng bộ hóa chính xác.


2
+1, Đó là những gì bidict cơ bản nào, cộng với đường cho việc truy cập các bản đồ nghịch đảo bằng cách sử dụng mydict[:value]để có được key(với chi phí của một số hiệu suất)
Tobias KIENZLER

6

Bạn có hai vấn đề riêng biệt.

  1. Bạn có một đối tượng "Hội thoại". Nó đề cập đến hai Ngôi vị. Vì một người có thể có nhiều cuộc trò chuyện, bạn có một mối quan hệ nhiều-nhiều.

  2. Bạn có Bản đồ từ Người đến danh sách Cuộc trò chuyện. Một Chuyển đổi sẽ có một cặp Người.

Làm điều gì đó như thế này

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

Không, thực sự không có cách nào để làm điều này mà không tạo hai từ điển. Làm thế nào để có thể thực hiện điều này chỉ với một từ điển trong khi vẫn tiếp tục cung cấp hiệu suất tương đương?

Tốt hơn hết là bạn nên tạo một kiểu tùy chỉnh bao gồm hai từ điển và hiển thị chức năng bạn muốn.


3

Một cách ít dài dòng hơn, vẫn sử dụng đảo ngược:

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


2

Một giải pháp khả thi khác là triển khai một lớp con của dict, chứa từ điển gốc và theo dõi phiên bản đảo ngược của nó. Giữ hai vùng riêng biệt có thể hữu ích nếu các khóa và giá trị trùng nhau.

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. 

Thí dụ:

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

Có thư viện mở rộng bộ sưu tập trên pypi: https://pypi.python.org/pypi/collections-extended/0.6.0

Sử dụng lớp bijection dễ dàng như:

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

Tôi thích đề xuất của bidict trong một trong các bình luận.

pip install bidict

Sử dụng:

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

Vì không có nhiều tài liệu về nó. Nhưng tôi có tất cả các tính năng tôi cần từ nó hoạt động bình thường.

Bản in:

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

1

Mô-đun mở rộng kjbuckets C cung cấp cấu trúc dữ liệu "đồ thị" mà tôi tin rằng sẽ cung cấp cho bạn những gì bạn muốn.


Xin lỗi, tôi không đề cập đến nó, nhưng nó trên công cụ ứng dụng ... vì vậy không có phần mở rộng C.
Sudhir Jonathan

1

Dưới đây là một cách triển khai từ điển hai chiều nữa bằng cách mở rộng dictlớp pythons trong trường hợp bạn không thích bất kỳ lớp nào khác trong số đó:

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)

Sử dụng nó như một từ điển python bình thường ngoại trừ trong xây dựng:

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

1

Một cách tôi thích làm loại việc này là:

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

1
Chào mừng bạn đến với StackOverflow! Vui lòng chỉnh sửa câu trả lời của bạn và bổ sung thêm giải thích cho mã của bạn, tốt hơn là thêm mô tả tại sao nó khác với 14 câu trả lời khác. Câu hỏi đã có hơn mười năm tuổi và đã có một câu trả lời được chấp nhận và nhiều câu trả lời được giải thích rõ ràng, được ủng hộ nhiệt tình. Nếu không có thêm chi tiết trong bài đăng của bạn, nó có chất lượng tương đối thấp hơn và có thể sẽ bị đánh giá thấp hoặc bị xóa. Thêm thông tin bổ sung đó sẽ giúp chứng minh sự tồn tại của câu trả lời của bạn.
Das_Geek
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.