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?
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 :
bd.inverse
se met à jour automatiquement lorsque la variable dict bd
est modifiée.bd.inverse[value]
est toujours un list de key
tel que bd[key] == value
.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']}
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)
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:
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
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
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]