Carte bidirectionnelle / inversée [dupliquer]

101

Je fais ce truc de standard en python où je dois garder une trace de qui parle à qui, donc si Alice -> Bob, alors cela implique que Bob -> Alice.

Oui, je pourrais remplir deux cartes de hachage, mais je me demande si quelqu'un a une idée pour le faire avec une seule.

Ou suggérez une autre structure de données.

Il n'y a pas de conversations multiples. Disons que c'est pour un centre d'appels du service client, alors quand Alice se connecte au standard, elle ne parlera qu'à Bob. Ses réponses ne vont également qu'à elle.

Sudhir Jonathan
la source
15
notez que vous décrivez une carte bijective.
Nick Dandoulakis
Voici une implémentation de dictionnaire bijective simple , même si je ne sais pas si elle répondra à vos exigences de performance. (Lien de cet article de blog sur Boost Bimap pour Python , qui contient une bonne discussion sur le sujet.)
System PAUSE
2
Si Alice parle à Bob, je suppose qu'elle ne peut pas aussi parler à Charles; Bob ne peut pas non plus parler à quelqu'un d'autre? De plus, combien de personnes et combien de conversations pouvez-vous avoir à un moment donné?
système PAUSE
Non ... pas sur mon standard. Tout message qu'Alice m'enverra devra être adressé à Bob. C'est juste que je vais acheminer des milliers de conversations simultanées. Mais chaque personne ne parle qu'à une seule autre personne à la fois.
Sudhir Jonathan
1
Non ... j'ai juste besoin d'acheminer les messages du client vers l'opérateur et vice versa ... sans même stocker les conversations en aucune façon.
Sudhir Jonathan le

Réponses:

90

Vous pouvez créer votre propre type de dictionnaire en sous dict- classant et en ajoutant la logique souhaitée. Voici un exemple de base:

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

Et ça marche comme ça:

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

Je suis sûr que je n'ai pas couvert tous les cas, mais cela devrait vous aider à démarrer.

Sasha Chedygov
la source
2
@SudhirJonathan: Vous pouvez aller beaucoup plus loin avec cette idée - par exemple, ajoutez une .addméthode pour que vous puissiez faire des choses comme d.add('Bob', 'Alice')au lieu d'utiliser la syntaxe que j'ai montrée. J'inclurais également une gestion des erreurs. Mais vous avez l'idée de base. :)
Sasha Chedygov
1
Je suppose que cela relève de ces ajouts, mais il serait utile de supprimer les anciennes paires de clés lors de la définition de nouvelles ( d['foo'] = 'baz'il faudrait en outre supprimer la barclé).
barbe le
@TobiasKienzler: Vous avez raison, mais cela a été répertorié comme une hypothèse dans la question.
Sasha Chedygov
5
Il convient également de mentionner: le sous-classement dictproduit ici un comportement trompeur, car si vous créez l'objet avec un contenu initial, la structure sera rompue. __init__doit être annulée pour permettre à une construction comme d = TwoWayDict({'foo' : 'bar'})de fonctionner correctement.
Henry Keiter
19
Je veux juste mentionner qu'il ya une bibliothèque pour cela: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719
45

Dans votre cas particulier, vous pouvez stocker les deux dans un dictionnaire:

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

Puisque ce que vous décrivez est une relation symétrique. A -> B => B -> A

Nadia Alramli
la source
3
Hmm ... ouais, j'aime celui-ci le meilleur. J'essayais d'éviter de faire deux entrées, mais c'est la meilleure idée à ce jour.
Sudhir Jonathan
1
Je pense toujours qu'une carte bidirectionnelle devrait être possible: - /
Sudhir Jonathan
Si cela doit être efficace, alors sous les couvertures, vous avez besoin que les deux clés soient indexées dans une structure de données d'index - que ce soit un hachage, une liste triée, un arbre binaire, un trie, un tableau de suffixes plein de sistrings, ou quelque chose même plus exotique. Le moyen le plus simple de le faire en Python est d'utiliser un hachage.
Kragen Javier Sitaker
@SudhirJonathan Si vous préférez une carte vraiment bidirectionnelle, jetez un œil à bidict comme mentionné par exemple dans cette question - notez les problèmes de performance discutés par Aya dans les commentaires sur ma question de dupe .
Tobias Kienzler
25

Je sais que c'est une question plus ancienne, mais je voulais mentionner une autre excellente solution à ce problème, à savoir le package python bidict . C'est extrêmement simple à utiliser:

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])
Nearoo
la source
24

Je remplirais juste un deuxième hachage, avec

reverse_map = dict((reversed(item) for item in forward_map.items()))
Ian Clelland
la source
7
J'ai quelques parenthèses supplémentaires là-dedans:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk
1
Belle manière simple si vous ne mettez pas à jour davantage le dict. J'ai utilisémy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly
Lorsque vous utilisez ce code en Python 3 je reçois un avertissement: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Des idées pour se débarrasser de l'avertissement?
Kerwin Sneijders
9

Deux cartes de hachage sont probablement la solution la plus performante en supposant que vous puissiez économiser la mémoire. Je les regrouperais dans une seule classe - le fardeau du programmeur est de s'assurer que deux cartes de hachage se synchronisent correctement.

Triptyque
la source
2
+1, c'est ce que fait essentiellement bidict , plus le sucre pour accéder à la cartographie inverse en utilisant mydict[:value]pour obtenir key(au prix de certaines performances)
Tobias Kienzler
6

Vous avez deux problèmes distincts.

  1. Vous avez un objet "Conversation". Il fait référence à deux personnes. Puisqu'une personne peut avoir plusieurs conversations, vous avez une relation plusieurs-à-plusieurs.

  2. Vous avez une carte de personne à une liste de conversations. Une conversion aura une paire de personnes.

Faites quelque chose comme ça

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 )
S.Lott
la source
5

Non, il n'y a vraiment aucun moyen de faire cela sans créer deux dictionnaires. Comment serait-il possible de l'implémenter avec un seul dictionnaire tout en continuant à offrir des performances comparables?

Il vaut mieux créer un type personnalisé qui encapsule deux dictionnaires et expose la fonctionnalité souhaitée.

Andrew Hare
la source
3

Une manière moins verbeuse, toujours en utilisant inversé:

dict(map(reversed, my_dict.items()))
Eti JS
la source
2

Une autre solution possible consiste à implémenter une sous-classe de dict, qui contient le dictionnaire d'origine et garde une trace d'une version inversée de celui-ci. Garder deux dictionnaires séparés peut être utile si les clés et les valeurs se chevauchent.

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. 

Exemple:

>>> 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'}
Akavall
la source
2

Il y a la bibliothèque étendue des collections sur pypi: https://pypi.python.org/pypi/collections-extended/0.6.0

Utiliser la classe de bijection est aussi simple que:

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
Schwolop
la source
2

J'aime la suggestion de bidict dans l'un des commentaires.

pip install bidict

Utilisation:

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

Puisqu'il n'y a pas beaucoup de documentation à ce sujet. Mais toutes les fonctionnalités dont j'ai besoin fonctionnent correctement.

Impressions:

abcbadefghei
bidict({'a': 'b', 'b': 'n', 'c': 'h', 'd': 'i', 'e': 'o', 'f': 'd', 'g': 'f', 'h': 'j', 'i': 's'})
bnhnbiodfjos
AlgébriqueGéométrieÉtudiant
la source
1

Le module d'extension kjbuckets C fournit une structure de données "graphique" qui, je crois, vous donne ce que vous voulez.

Kragen Javier Sitaker
la source
Désolé, je ne l'ai pas mentionné, mais c'est sur le moteur d'application ... donc pas d'extensions C.
Sudhir Jonathan le
1

Voici une autre implémentation de dictionnaire bidirectionnel en étendant la dictclasse pythons au cas où vous n'aimeriez aucune de ces autres:

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)

Utilisez-le comme un dictionnaire python normal sauf en construction:

dd = DoubleD()
dd['foo'] = 'bar'
browlm13
la source
1

Une façon dont j'aime faire ce genre de chose est quelque chose comme:

{my_dict[key]: key for key in my_dict.keys()}
Tobi Abiodun
la source
1
Bienvenue dans StackOverflow! Veuillez modifier votre réponse et ajouter plus d'explications pour votre code, de préférence également en ajoutant une description expliquant pourquoi il est différent des 14 autres réponses. La question a plus de dix ans et a déjà une réponse acceptée et de nombreuses réponses bien expliquées et bien votées. Sans plus de détails dans votre message, il est de qualité comparativement inférieure et sera probablement rejeté ou supprimé. L'ajout de ces informations supplémentaires aidera à justifier l'existence de votre réponse.
Das_Geek