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


101

Я роблю цю справу з комутацією в python, де мені потрібно відслідковувати, хто з ким спілкується, тож якщо Аліса -> Боб, то це означає, що Боб -> Аліса.

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

Або запропонувати іншу структуру даних.

Немає кількох розмов. Скажімо, це для центру обслуговування клієнтів, тож коли Аліса набирає номер у комутаційному щиті, вона збирається поговорити лише з Боб. Його відповіді також стосуються лише її.


15
зауважте, що ви описуєте бієктивну карту.
Нік Дандулакіс

Ось простий варіант біективного словника , хоча я не знаю, чи відповідає він вашим вимогам щодо продуктивності. (Посилання від цієї статті в блозі про збільшення Bimap для Python , де є приємне обговорення цієї теми.)
система ПАУЗА

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')замість синтаксису, який я показав. Я б також включив деякі поводження з помилками. Але ви отримуєте основну думку. :)
Саша Чедигов

1
Я думаю, це підпадає під ці доповнення, але було б корисно видалити старі пари ключів при встановленні нових ( d['foo'] = 'baz'потрібно буде додатково видалити barключ).
борода

@TobiasKienzler: Ви праві, але це було зазначено як припущення у питанні.
Саша Чедигов

5
Також варто згадати: підкласифікація dictвиробляє тут деяку оманливу поведінку, оскільки якщо створити об’єкт з деяким початковим вмістом, структура буде порушена. __init__потрібно відміняти, щоб конструкція любила d = TwoWayDict({'foo' : 'bar'})працювати належним чином.
Генрі Кейтер

19
Відразу хочу відзначити , що існує бібліотека для цього: pip install bidict. URL: pypi.python.org/pypi/bidict
користувач1036719

45

У вашому спеціальному випадку ви можете зберігати обидва в одному словнику:

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

Оскільки те, що ви описуєте, - це симетричні відносини. A -> B => B -> A


3
Хм ... так, мені це найкраще подобається. Намагався уникати двох записів, але це найкраща ідея поки що.
Sudhir Jonathan

1
Але все-таки думаю, що двостороння карта повинна бути можливою: - /
Sudhir Jonathan

Якщо це повинно бути ефективним, то під обкладинками вам потрібно індексувати обидві клавіші в якійсь структурі даних індексу - будь то хеш, відсортований список, двійкове дерево, трие, суфіксний масив, наповнений сестрингами, чи щось навіть більш екзотичні. Найпростіший спосіб зробити це в Python - використовувати хеш.
Краген Хав'єр Сітакер

@SudhirJonathan Якщо ви віддаєте перевагу справді двосторонню карту, погляньте на бідікт, як згадувалося, наприклад, у цьому запитанні - зауважте, що про ефективність обговорювала Ая в коментарях до мого питання про дупа .
Тобіас Кіенцлер

25

Я знаю, що це давнє питання, але я хотів зазначити ще одне чудове рішення цієї проблеми, а саме пакет python 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())
Андрій Дроздюк

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]]). Будь-які ідеї, як позбутися попередження?
Кервін Снайдердерс

9

Дві карти хеша - це, мабуть, найшвидше рішення, якщо ви можете зекономити пам'ять. Я б переклав їх в один клас - тягар на програміста полягає в тому, щоб дві хеш-карти правильно синхронізувалися.


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

Менш багатослівний спосіб, як і раніше використовується зворотний:

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

Мені подобається пропозиція бідікт в одному з коментарів.

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.
Sudhir Jonathan

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'

1

Спосіб, який я люблю робити подібні речі, є чимось на кшталт:

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

1
Ласкаво просимо до StackOverflow! Будь ласка, відредагуйте свою відповідь та додайте більше пояснень до свого коду, бажано також додайте опис того, чому він відрізняється від 14 інших відповідей. Питання старше десяти років , і вже є прийнятою відповіддю і багатьма добре поясненими, добре підкріпленими. Без більш детальної інформації у вашій посаді вона порівняно нижчої якості і, ймовірно, буде скасована або видалена. Додавання додаткової інформації допоможе виправдати існування вашої відповіді.
Das_Geek
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.