Двусторонняя / обратная карта [дубликат]

101

Я делаю этот коммутатор на Python, где мне нужно отслеживать, кто с кем разговаривает, поэтому, если Алиса -> Боб, то это означает, что Боб -> Алиса.

Да, я мог бы заполнить две хеш-карты, но мне интересно, есть ли у кого-нибудь идея сделать это с одной.

Или предложите другую структуру данных.

Нет нескольких разговоров. Допустим, это для центра обслуживания клиентов, поэтому, когда Алиса набирает номер на коммутаторе, она будет говорить только с Бобом. Его ответы тоже идут только ей.

Судхир Джонатан
источник
15
обратите внимание, что вы описываете биективную карту.
Ник Дандулакис,
Вот простая реализация биективного словаря , хотя я не знаю, будет ли она соответствовать вашим требованиям к производительности. (Ссылка на статью в блоге о boost Bimap для Python , в которой есть хорошее обсуждение этой темы.)
система ПАУЗА
2
Если Алиса разговаривает с Бобом, я так понимаю, что она не может также разговаривать с Чарльзом; и Боб не может разговаривать ни с кем другим? Кроме того, сколько людей и сколько разговоров вы можете вести в любой момент времени?
система ПАУЗА
Нет ... не на моем коммутаторе. Любое сообщение, которое пришлет мне Алиса, должно быть отправлено Бобу. Просто я буду маршрутизировать тысячи одновременных разговоров. Но каждый человек одновременно разговаривает только с одним человеком.
Судир Джонатан
1
Нет ... Мне просто нужно маршрутизировать сообщения клиента оператору и наоборот ... даже не сохраняя разговоры в любом случае.
Судир Джонатан,

Ответы:

91

Вы можете создать свой собственный тип словаря, создав подклассы 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')вместо того, чтобы использовать синтаксис, который я показал. Я бы также включил некоторую обработку ошибок. Но вы поняли основную идею. :)
Саша Чедыгов 07
1
Я предполагаю, что это подпадает под эти дополнения, но было бы полезно удалить старые пары ключей при установке новых ( d['foo'] = 'baz'потребуется дополнительно удалить barключ).
beardc
@TobiasKienzler: Вы правы, но это было указано в вопросе как предположение.
Саша Чедыгов
5
Также стоит упомянуть: создание подклассов здесь dictприводит к некоторому обманчивому поведению, потому что если вы создадите объект с некоторым начальным содержимым, структура будет нарушена. __init__необходимо переопределить, чтобы конструкция вроде d = TwoWayDict({'foo' : 'bar'})работала правильно.
Генри Кейтер
19
Сразу хочу отметить , что существует библиотека для этого: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719,
45

В вашем особом случае вы можете хранить оба в одном словаре:

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

Поскольку то, что вы описываете, является симметричным отношением. A -> B => B -> A

Надя Алрамли
источник
3
Хм ... да, мне нравится этот больше всего. Пытался избежать двух записей, но пока это лучшая идея.
Судир Джонатан
1
Тем не менее думаю, что двусторонняя карта должна быть возможна: - /
Судир Джонатан
Если он должен быть эффективным, то вам нужно, чтобы оба ключа были проиндексированы в некоторой структуре данных индекса - будь то хэш, отсортированный список, двоичное дерево, дерево, суффиксный массив, полный строк, или что-то даже более экзотично. Самый простой способ сделать это в Python - использовать хеш.
Kragen Javier Sitaker,
@SudhirJonathan Если вы предпочитаете по-настоящему двустороннюю карту, взгляните на bidict, как указано, например, в этом вопросе - обратите внимание на проблемы с производительностью, обсуждаемые Аей в комментариях к моему вопросу об обмане .
Тобиас Кинцлер
25

Я знаю, что это более старый вопрос, но я хотел бы упомянуть еще одно отличное решение этой проблемы, а именно двунаправленный пакет python . Очень просто использовать:

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])
Nearoo
источник
24

Я бы просто заполнил второй хеш с помощью

reverse_map = dict((reversed(item) for item in forward_map.items()))
Ян Клелланд
источник
7
Есть дополнительные скобки:reverse_map = dict(reversed(item) for item in forward_map.items())
Андрей Дроздюк
1
Хороший простой способ, если вы больше не будете обновлять dict. Я использовалmy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly
При использовании этого кода в Python 3 , я получаю предупреждение: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Есть идеи, как избавиться от предупреждения?
Кервин Снейдерс,
9

Две хэш-карты - это, вероятно, самое быстрое решение, если вы можете сэкономить память. Я бы обернул их в один класс - задача программиста состоит в том, чтобы обеспечить правильную синхронизацию двух хэш-карт.

Триптих
источник
2
+1, Это то, что в основном делает бидикт , плюс сахар для доступа к обратному отображению с использованием mydict[:value]для получения key(за счет некоторой производительности)
Тобиас Кинцлер
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

Менее подробный способ с использованием обратного:

dict(map(reversed, my_dict.items()))
Eti JS
источник
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 есть расширенная библиотека коллекций: 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

Мне нравится предложение бидикта в одном из комментариев.

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 предоставляет «графическую» структуру данных, которая, как мне кажется, дает вам то, что вы хотите.

Краген Хавьер Ситакер
источник
Извините, я не упомянул об этом, но это на движке приложений ... так что никаких расширений C.
Судир Джонатан,
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)

Используйте его как обычный словарь Python, за исключением конструкции:

dd = DoubleD()
dd['foo'] = 'bar'
Browlm13
источник
1

Мне нравится делать такие вещи примерно так:

{my_dict[key]: key for key in my_dict.keys()}
Тоби Абиодун
источник
1
Добро пожаловать в StackOverflow! Пожалуйста , измените свой ответ и добавить дополнительные пояснения для вашего кода, предпочтительно также добавлять описание, почему он отличается от других 14 ответов. Этому вопросу более десяти лет , и на него уже есть принятый ответ, а также множество хорошо объясненных и получивших хорошие отзывы. Без более подробной информации в вашем сообщении он будет сравнительно более низкого качества и, вероятно, будет отклонен или удален. Добавление этой дополнительной информации поможет оправдать существование вашего ответа.
Das_Geek