web-dev-qa-db-fra.com

Comment implémenter une table de hachage bidirectionnelle efficace?

Python dict est une structure de données très utile:

d = {'a': 1, 'b': 2}

d['a'] # get 1

Parfois, vous souhaitez également indexer par des valeurs.

d[1] # get 'a'

Quel est le moyen le plus efficace d'implémenter cette structure de données? Un officiel recommande-t-il de le faire?

53
Juanjo Conti

Voici une classe pour une dict bidirectionnelle, inspirée de Recherche d'une clé dans un dictionnaire Python et modifiée pour permettre les valeurs 2) et 3) suivantes.

Notez que :

  • 1) Le répertoire inverse _ bd.inverse se met à jour automatiquement lorsque la variable dict bd est modifiée.
  • 2) Le répertoire inversebd.inverse[value] est toujours un list de key tel que bd[key] == value.
  • 3) Contrairement au module bidict de https://pypi.python.org/pypi/bidict , ici nous pouvons avoir 2 clés ayant la même valeur, c'est très important _.

Code:

class bidict(dict):
    def __init__(self, *args, **kwargs):
        super(bidict, self).__init__(*args, **kwargs)
        self.inverse = {}
        for key, value in self.items():
            self.inverse.setdefault(value,[]).append(key) 

    def __setitem__(self, key, value):
        if key in self:
            self.inverse[self[key]].remove(key) 
        super(bidict, self).__setitem__(key, value)
        self.inverse.setdefault(value,[]).append(key)        

    def __delitem__(self, key):
        self.inverse.setdefault(self[key],[]).remove(key)
        if self[key] in self.inverse and not self.inverse[self[key]]: 
            del self.inverse[self[key]]
        super(bidict, self).__delitem__(key)

Exemple d'utilisation:

bd = bidict({'a': 1, 'b': 2})  
print(bd)                     # {'a': 1, 'b': 2}                 
print(bd.inverse)             # {1: ['a'], 2: ['b']}
bd['c'] = 1                   # Now two keys have the same value (= 1)
print(bd)                     # {'a': 1, 'c': 1, 'b': 2}
print(bd.inverse)             # {1: ['a', 'c'], 2: ['b']}
del bd['c']
print(bd)                     # {'a': 1, 'b': 2}
print(bd.inverse)             # {1: ['a'], 2: ['b']}
del bd['a']
print(bd)                     # {'b': 2}
print(bd.inverse)             # {2: ['b']}
bd['b'] = 3
print(bd)                     # {'b': 3}
print(bd.inverse)             # {2: [], 3: ['b']}
41
Basj

Vous pouvez utiliser le même dicto en ajoutant la clé et la paire de valeurs dans l’ordre inverse.

 d = {'a': 1, 'b': 2} 
 revd = dict ([inversé (i) pour i dans d.items ()]) 
 d.update (revd)
32
Emil

La table de hachage bidirectionnelle d'un pauvre consisterait à n'utiliser que deux dictionnaires (ce sont déjà des infrastructures de données hautement ajustées).

Il y a aussi un paquet bidict sur l'index:

La source de bidict se trouve sur github:

31
miku

Le fragment de code ci-dessous implémente une carte inversible (bijective):

class BijectionError(Exception):
    """Must set a unique value in a BijectiveMap."""

    def __init__(self, value):
        self.value = value
        msg = 'The value "{}" is already in the mapping.'
        super().__init__(msg.format(value))


class BijectiveMap(dict):
    """Invertible map."""

    def __init__(self, inverse=None):
        if inverse is None:
            inverse = self.__class__(inverse=self)
        self.inverse = inverse

    def __setitem__(self, key, value):
        if value in self.inverse:
            raise BijectionError(value)

        self.inverse._set_item(value, key)
        self._set_item(key, value)

    def __delitem__(self, key):
        self.inverse._del_item(self[key])
        self._del_item(key)

    def _del_item(self, key):
        super().__delitem__(key)

    def _set_item(self, key, value):
        super().__setitem__(key, value)

L'avantage de cette implémentation est que l'attribut inverse d'un BijectiveMap est à nouveau un BijectiveMap. Par conséquent, vous pouvez faire des choses comme:

>>> foo = BijectiveMap()
>>> foo['steve'] = 42
>>> foo.inverse
{42: 'steve'}
>>> foo.inverse.inverse
{'steve': 42}
>>> foo.inverse.inverse is foo
True
2
jme

Quelque chose comme ça, peut-être:

import itertools

class BidirDict(dict):
    def __init__(self, iterable=(), **kwargs):
        self.update(iterable, **kwargs)
    def update(self, iterable=(), **kwargs):
        if hasattr(iterable, 'iteritems'):
            iterable = iterable.iteritems()
        for (key, value) in itertools.chain(iterable, kwargs.iteritems()):
            self[key] = value
    def __setitem__(self, key, value):
        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):
        value = self[key]
        dict.__delitem__(self, key)
        dict.__delitem__(self, value)
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, dict.__repr__(self))

Vous devez décider ce que vous voulez qu'il se passe si plusieurs clés ont une valeur donnée. la bidirectionnalité d'une paire donnée pourrait facilement être gâtée par une paire ultérieure que vous avez insérée. J'ai implémenté un choix possible.


Exemple :

bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'})
print bd['myvalue1']   # a
print bd['myvalue2']   # b        
1
Matt Anderson

Tout d'abord, vous devez vous assurer que la clé de la correspondance de valeurs est une à une, sinon, il n'est pas possible de créer une carte bidirectionnelle.

Deuxièmement, quelle est la taille de l'ensemble de données? S'il n'y a pas beaucoup de données, utilisez simplement 2 cartes distinctes et mettez-les à jour lors de la mise à jour. Ou mieux, utilisez une solution existante telle que Bidict , qui n’est qu’une enveloppe de 2 cartes, avec mise à jour/suppression intégrée.

Mais si le jeu de données est volumineux, il n'est pas souhaitable de maintenir 2 dict:

  • Si la clé et la valeur sont numériques, envisagez la possibilité d'utiliser Interpolation pour approcher le mappage. Si la grande majorité des paires clé-valeur Peuvent être couvertes par la fonction de mappage (et ses
    reverse), il vous suffit alors d’enregistrer les valeurs aberrantes sur des cartes.

  • Si la plupart des accès sont unidirectionnels (clé-> valeur), alors c'est totalement Ok pour construire la carte inversée de manière incrémentielle,
    espace.

Code:

d = {1: "one", 2: "two" }
reverse = {}

def get_key_by_value(v):
    if v not in reverse:
        for _k, _v in d.items():
           if _v == v:
               reverse[_v] = _k
               break
    return reverse[v]
0
NeoWang