Python dict
es una estructura de datos muy útil:
d = {'a': 1, 'b': 2}
d['a'] # get 1
A veces también le gustaría indexar por valores.
d[1] # get 'a'
¿Cuál es la forma más eficiente de implementar esta estructura de datos? ¿Alguna forma oficial que recomiende hacerlo?
python
hashtable
bidirectional
Juanjo Conti
fuente
fuente
{1: ['a', 'A'], 2: 'b'}
. Vea mi respuesta para saber cómo hacerlo.Respuestas:
Aquí hay una clase para un bidireccional
dict
, inspirada en Finding key from value en el diccionario Python y modificada para permitir los siguientes 2) y 3).Tenga en cuenta que :
bd.inverse
actualiza automáticamente cuandobd
se modifica el diccionario estándar .bd.inverse[value]
es siempre una lista dekey
tal quebd[key] == value
.bidict
módulo de https://pypi.python.org/pypi/bidict , aquí podemos tener 2 claves con el mismo valor, esto es muy importante .Código:
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)
Ejemplo de uso:
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']}
fuente
self[key]
in__delitem__()
con una solavalue = self[key]
asignación reutilizada para tales búsquedas. Pero ... si. Eso es insignificante. ¡Gracias por lo increíble, Basj !Puede usar el mismo dictado agregando clave, par de valores en orden inverso.
fuente
d.update( dict((d[k], k) for k in d) )
.dict((v, k) for (k, v) in d.items())
. En cualquier caso, puede pasar directamente a pares .Update:d.update(reversed(i) for i in d.items())
.d={'a':1, 'b':2, 1: 'b'}
dict(map(reversed, a_dict.items()))
.d.update(revd)
Sin embargo, dado que las dos primeras líneas de esta respuesta ignorando el final son geniales, todavía estoy contemplando un voto a favor. Pensemos un poco en esto.Una tabla hash bidireccional de un hombre pobre sería usar solo dos diccionarios (estas ya son estructuras de datos altamente ajustadas).
También hay un paquete bidict en el índice:
La fuente de bidict se puede encontrar en github:
fuente
El siguiente fragmento de código implementa un mapa invertible (biyectiva):
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)
La ventaja de esta implementación es que el
inverse
atributo de aBijectiveMap
es nuevamente aBijectiveMap
. Por lo tanto, puede hacer cosas como:>>> foo = BijectiveMap() >>> foo['steve'] = 42 >>> foo.inverse {42: 'steve'} >>> foo.inverse.inverse {'steve': 42} >>> foo.inverse.inverse is foo True
fuente
Algo como esto, tal vez:
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))
Tienes que decidir qué quieres que suceda si más de una clave tiene un valor determinado; la bidireccionalidad de un par dado podría ser fácilmente superada por algún par posterior que inserte. Implementé una opción posible.
Ejemplo:
bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'}) print bd['myvalue1'] # a print bd['myvalue2'] # b
fuente
dict([('a', 'b'), ('b', 'c')]); dict['b']
-> en'c'
lugar de la clave'a'
.print bd['myvalue2']
respuestasb, c
(o[b, c]
, o(b, c)
, o cualquier otra cosa)?Primero, debe asegurarse de que la clave para el mapeo de valores sea uno a uno; de lo contrario, no es posible construir un mapa bidireccional.
En segundo lugar, ¿qué tamaño tiene el conjunto de datos? Si no hay muchos datos, solo use 2 mapas separados y actualice ambos cuando actualice. O mejor, use una solución existente como Bidict , que es solo una envoltura de 2 dictados, con actualización / eliminación incorporada.
Pero si el conjunto de datos es grande y no es deseable mantener 2 dictados:
Si tanto la clave como el valor son numéricos, considere la posibilidad de utilizar la interpolación para aproximar la asignación. Si la función de mapeo (y su
función inversa) puede cubrir la gran mayoría de los pares clave-valor , entonces solo necesita registrar los valores atípicos en los mapas.
Si la mayor parte del acceso es unidireccional (clave-> valor), entonces está totalmente bien construir el mapa inverso de forma incremental, para cambiar tiempo por
espacio.
Código:
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]
fuente
Desafortunadamente, la respuesta mejor calificada
bidict
no funciona.Hay tres opciones:
Dict de subclase : puede crear una subclase de
dict
, pero tenga cuidado. Usted tiene que escribir implementaciones personalizadas deupdate
,pop
,initializer
,setdefault
. Lasdict
implementaciones no llaman__setitem__
. Es por eso que la respuesta mejor calificada tiene problemas.Heredar de UserDict : esto es como un dict, excepto que todas las rutinas están hechas para llamar correctamente. Utiliza un dict debajo del capó, en un elemento llamado
data
. Puede leer la documentación de Python o usar una implementación simple de una lista direccional que funciona en Python 3 . Perdón por no incluirlo literalmente: no estoy seguro de sus derechos de autor.Heredar de clases base abstractas : Heredar de collections.abc lo ayudará a obtener todos los protocolos e implementaciones correctos para una nueva clase. Esto es excesivo para un diccionario bidireccional, a menos que también pueda cifrar y almacenar en caché una base de datos.
TL; DR: use esto para su código. Leer Trey Hunner 's artículo para más detalles.
fuente