Invertir / invertir una asignación de diccionario

663

Dado un diccionario como este:

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

¿Cómo se puede invertir este mapa para obtener:

inv_map = {1: 'a', 2: 'b'}
Brian M. Hunt
fuente

Respuestas:

924

Para Python 2.7.x

inv_map = {v: k for k, v in my_map.iteritems()}

Para Python 3+:

inv_map = {v: k for k, v in my_map.items()}
SilentGhost
fuente
44
En versiones recientes de Python 2.7.x también my_map.items()funciona
valentin
30
Esto funcionará excepto que no funcionará si no hay unicidad en los valores. En ese caso
perderá
2
Sí, como detalle de implementación. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. No hay garantía de que se mantenga así, así que no escriba código confiando en Dicttener el mismo comportamiento que OrderedDict.
Mattias
99
@Mattias, esto es cierto para Python 3.6. Para la versión 3.7, la conservación de pedidos es oficial: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL lo dijo.
InterDist
174

Suponiendo que los valores en el dict son únicos:

dict((v, k) for k, v in my_map.iteritems())
Rick apoya a Monica
fuente
22
Los valores también deben ser modificables
John La Rooy
30
@ Buttons840: Si los valores no son únicos, de todos modos no hay una inversión única del diccionario o, en otras palabras, invertir no tiene sentido.
Wrzlprmft
2
@ Botones840 Solo aparecerá la última clave para el valor. Probablemente no haya garantías sobre el orden que iteritems()saldrá, por lo que se puede suponer que se asignará una clave arbitraria para un valor no único, de una manera que aparentemente será reproducible en algunas condiciones, pero en general no.
Evgeni Sergeev
2
Tenga en cuenta, por supuesto, que en Python 3 ya no hay un iteritems()método y este enfoque no funcionará; use en items()su lugar como se muestra en la respuesta aceptada. Además, una comprensión del diccionario lo haría más bonito que llamar dict.
Mark Amery
55
@Wrzlprmft Existe una definición natural para inversa en el caso de valores no únicos. Cada valor se asigna al conjunto de claves que lo conducen.
Leo
135

Si los valores en my_mapno son únicos:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)
Robert Rossney
fuente
56
... o simplemente inv_map.setdefault (v, []). append (k). Solía ​​ser un fanboy por defecto, pero luego me jodieron demasiadas veces y llegué a la conclusión de que en realidad explícito es mejor que implícito.
alsuren
Esta respuesta es incorrecta para mapas múltiples, agregar aquí es inútil porque el valor se restablece a la lista vacía cada vez, debe usar set_default
Yaroslav Bulatov
1
@YaroslavBulatov no, el código que se muestra aquí no está roto: inv_map.get(v, [])devuelve la lista ya agregada si hay una, por lo que la asignación no se restablece a una lista vacía. setdefaultsin embargo, aún sería más bonita.
Mark Amery
10
Un conjunto tendría más sentido aquí. Las claves son (probablemente) hashable, y no hay orden. inv_map.setdefault(v, set()).add(k).
Artyer
1
En python3, use en my_map.items()lugar de my_map.iteritems().
apitsch
42

Para hacer esto mientras se preserva el tipo de su mapeo (suponiendo que sea una dicto una dictsubclase):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))
fs.
fuente
44
Puede ser inteligente, pero no funciona cuando más de una clave tiene el mismo valor en el diccionario original.
Rafael_Espericueta
1
@Rafael_Espericueta Eso es cierto de cualquier posible respuesta a esta pregunta, ya que un mapa con valores repetidos no es invertible.
Mark Amery
2
@ Mark_Amery Puede ser invertible de manera más general, en cierto sentido. Por ejemplo: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D es un diccionario de, por ejemplo, {parent: children}, mientras que Dinv es el diccionario {child: parents}.
Rafael_Espericueta
36

Prueba esto:

inv_map = dict(zip(my_map.values(), my_map.keys()))

(Tenga en cuenta que los documentos de Python en las vistas de diccionario garantizan explícitamente que .keys()y.values() tienen sus elementos en el mismo orden, lo que permite que funcione el enfoque anterior).

Alternativamente:

inv_map = dict((my_map[k], k) for k in my_map)

o usando las comprensiones dict de python 3.0

inv_map = {my_map[k] : k for k in my_map}
sykora
fuente
1
Tenga en cuenta que esto solo funciona si las claves son únicas (que casi nunca es el caso si desea invertirlas).
Gented
De acuerdo con python.org/dev/peps/pep-0274, las comprensiones dict también están disponibles en 2.7+.
Kawu
24

Otra forma más funcional:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Brendan Maguire
fuente
3
Gracias por publicar. No estoy seguro de que esto sea preferible, citando a Guido Van Rossum en PEP 279: " filtery mapdebería morir y ser incluido en las comprensiones de la lista, no crecer más variantes".
Brian M. Hunt
2
Sí, ese es un punto justo, Brian. Solo lo estaba agregando como punto de conversación. La forma de comprensión dict es más legible para la mayoría de lo que imagino. (Y probablemente más rápido también supongo)
Brendan Maguire
3
Puede ser menos legible que otros, pero de esta manera tiene la ventaja de poder intercambiarse dictcon otros tipos de mapeo como collections.OrderedDictocollections.defaultdict
Will S
10

Esto amplía la respuesta de Robert , aplicando cuando los valores en el dict no son únicos.

class ReversibleDict(dict):

    def reversed(self):
        """
        Return a reversed dict, with common values in the original dict
        grouped into a list in the returned dict.

        Example:
        >>> d = ReversibleDict({'a': 3, 'c': 2, 'b': 2, 'e': 3, 'd': 1, 'f': 2})
        >>> d.reversed()
        {1: ['d'], 2: ['c', 'b', 'f'], 3: ['a', 'e']}
        """

        revdict = {}
        for k, v in self.iteritems():
            revdict.setdefault(v, []).append(k)
        return revdict

La implementación es limitada, ya que no se puede usar reverseddos veces y recuperar el original. No es simétrico como tal. Se prueba con Python 2.6. Aquí hay un caso de uso de cómo estoy usando para imprimir el dict resultante.

Si prefiere usar a setque a list, y podría existir aplicaciones desordenadas para las que esto tiene sentido, en lugar desetdefault(v, []).append(k) usar setdefault(v, set()).add(k).

Acumenus
fuente
este también sería un buen lugar para usar conjuntos en lugar de listas, es decirrevdict.setdefault(v, set()).add(k)
mueslo
Por supuesto, pero eso es exactamente por qué es una buena razón para usar set. Es el tipo intrínseco que se aplica aquí. ¿Qué sucede si quiero encontrar todas las claves donde los valores no son 1o 2? Entonces solo puedo hacer d.keys() - inv_d[1] - inv_d[2](en Python 3)
mueslo
9

También podemos revertir un diccionario con claves duplicadas usando defaultdict:

from collections import Counter, defaultdict

def invert_dict(d):
    d_inv = defaultdict(list)
    for k, v in d.items():
        d_inv[v].append(k)
    return d_inv

text = 'aaa bbb ccc ddd aaa bbb ccc aaa' 
c = Counter(text.split()) # Counter({'aaa': 3, 'bbb': 2, 'ccc': 2, 'ddd': 1})
dict(invert_dict(c)) # {1: ['ddd'], 2: ['bbb', 'ccc'], 3: ['aaa']}  

Mira aquí :

Esta técnica es más simple y más rápida que una técnica equivalente dict.setdefault().

irudyak
fuente
6

Por ejemplo, tiene el siguiente diccionario:

dict = {'a': 'fire', 'b': 'ice', 'c': 'fire', 'd': 'water'}

Y quieres obtenerlo en una forma tan invertida:

inverted_dict = {'fire': ['a', 'c'], 'ice': ['b'], 'water': ['d']}

La primera solución . Para invertir pares clave-valor en su diccionario, utilice un forenfoque de bucle:

# Use this code to invert dictionaries that have non-unique values

inverted_dict = dict()
for key, value in dict.items():
    inverted_dict.setdefault(value, list()).append(key)

Segunda solución . Utilice un enfoque de comprensión del diccionario para la inversión:

# Use this code to invert dictionaries that have unique values

inverted_dict = {value: key for key, value in dict.items()}

Tercera solución . Utilice revertir el enfoque de inversión (se basa en la segunda solución):

# Use this code to invert dictionaries that have lists of values

dict = {value: key for key in inverted_dict for value in my_map[key]}
Andy
fuente
44
dictestá reservado y no debe usarse para nombres de variables
crypdick
2
olvidó decirnos qué my_mapes
crypdick
dictio()? Quiso decir dict()?
Georgy el
5

Combinación de lista y comprensión de diccionario. Puede manejar claves duplicadas

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}
SVJ
fuente
1
Al igual que stackoverflow.com/a/41861007/1709587 , esta es una solución O (n²) a un problema que se resuelve fácilmente en O (n) con un par de líneas adicionales de código.
Mark Amery
2

Si los valores no son únicos y eres un poco duro:

inv_map = dict(
    (v, [k for (k, xx) in filter(lambda (key, value): value == v, my_map.items())]) 
    for v in set(my_map.values())
)

Especialmente para un dict grande, tenga en cuenta que esta solución es mucho menos eficiente que la respuesta Python invertir / invertir una asignación porque se repite items()varias veces.

pcv
fuente
77
Esto es simplemente ilegible y un buen ejemplo de cómo no escribir código mantenible. No lo haré -1porque todavía responde la pregunta, solo mi opinión.
Russ Bradberry
1

Además de las otras funciones sugeridas anteriormente, si le gustan las lambdas:

invert = lambda mydict: {v:k for k, v in mydict.items()}

O también puedes hacerlo de esta manera:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
fuente
2
-1; todo lo que has hecho es tomar otras respuestas de la página y ponerlas en una lambda. Además, asignar una lambda a una variable es una violación de PEP 8 .
Mark Amery
1

Creo que la mejor manera de hacer esto es definir una clase. Aquí hay una implementación de un "diccionario simétrico":

class SymDict:
    def __init__(self):
        self.aToB = {}
        self.bToA = {}

    def assocAB(self, a, b):
        # Stores and returns a tuple (a,b) of overwritten bindings
        currB = None
        if a in self.aToB: currB = self.bToA[a]
        currA = None
        if b in self.bToA: currA = self.aToB[b]

        self.aToB[a] = b
        self.bToA[b] = a
        return (currA, currB)

    def lookupA(self, a):
        if a in self.aToB:
            return self.aToB[a]
        return None

    def lookupB(self, b):
        if b in self.bToA:
            return self.bToA[b]
        return None

Los métodos de eliminación e iteración son lo suficientemente fáciles de implementar si son necesarios.

Esta implementación es mucho más eficiente que invertir un diccionario completo (que parece ser la solución más popular en esta página). Sin mencionar que puede agregar o eliminar valores de su SymDict tanto como desee, y su diccionario inverso siempre será válido; esto no es cierto si simplemente invierte todo el diccionario una vez.

NcAdams
fuente
Me gusta esta idea, aunque sería bueno tener en cuenta que intercambia memoria adicional para lograr una mejor computación. Un medio más feliz puede ser el almacenamiento en caché o el cálculo lento del espejo. También vale la pena señalar que podría hacerse más sintácticamente atractivo con, por ejemplo, vistas de diccionario y operadores personalizados.
Brian M. Hunt
@ BrianM.Hunt Intercambia memoria, pero no mucho. Solo almacena dos conjuntos de punteros para cada objeto. Si sus objetos son mucho más grandes que los enteros individuales, esto no hará mucha diferencia. Si tiene enorme mesa de pequeños objetos, por otro lado, puede que tenga que tener en cuenta esas sugerencias ...
NcAdams
Y estoy de acuerdo, hay más por hacer aquí; podría desarrollar esto en un tipo de datos completamente funcional más adelante
NcAdams
2
"Esta implementación es mucho más eficiente que invertir un diccionario completo" - mmm, ¿por qué? No veo ninguna forma plausible de que este enfoque pueda tener un beneficio significativo en el rendimiento; todavía tienes dos diccionarios de esta manera. En todo caso, esperaría que esto sea más lento que, por ejemplo, invertir el dict con una comprensión, porque si invierte el dict, Python puede saber de antemano cuántos depósitos asignar en la estructura de datos C subyacente y crear el mapa inverso sin llamar nunca dictresize, pero este enfoque niega a Python esa posibilidad.
Mark Amery
1

Esto maneja valores no únicos y conserva gran parte del aspecto del caso único.

inv_map = {v:[k for k in my_map if my_map[k] == v] for v in my_map.itervalues()}

Para Python 3.x, reemplace itervaluescon values.

Ersatz Kwisatz
fuente
3
Esta solución es bastante elegante como un revestimiento único y gestiona el caso de valores no únicos. Sin embargo, tiene una complejidad en O (n2), lo que significa que debería estar bien para varias docenas de elementos, pero sería demasiado lento para un uso práctico si tiene varios cientos de miles de elementos en su diccionario inicial. Las soluciones basadas en un dict predeterminado son mucho más rápidas que esta.
gabuzo
Gabuzo tiene toda la razón. Esta versión es (posiblemente) más clara que algunas, pero no es adecuada para grandes datos.
Ersatz Kwisatz
0

La función es simétrica para los valores de la lista de tipos; Las tuplas se convierten en listas cuando se realiza reverse_dict (reverse_dict (diccionario))

def reverse_dict(dictionary):
    reverse_dict = {}
    for key, value in dictionary.iteritems():
        if not isinstance(value, (list, tuple)):
            value = [value]
        for val in value:
            reverse_dict[val] = reverse_dict.get(val, [])
            reverse_dict[val].append(key)
    for key, value in reverse_dict.iteritems():
        if len(value) == 1:
            reverse_dict[key] = value[0]
    return reverse_dict
Alf
fuente
0

Dado que los diccionarios requieren una clave única dentro del diccionario a diferencia de los valores, tenemos que agregar los valores invertidos en una lista de clasificación para incluirlos en las nuevas claves específicas.

def r_maping(dictionary):
    List_z=[]
    Map= {}
    for z, x in dictionary.iteritems(): #iterate through the keys and values
        Map.setdefault(x,List_z).append(z) #Setdefault is the same as dict[key]=default."The method returns the key value available in the dictionary and if given key is not available then it will return provided default value. Afterward, we will append into the default list our new values for the specific key.
    return Map
EyoelD
fuente
0

Solución funcional rápida para mapas no biyectivos (valores no únicos):

from itertools import imap, groupby

def fst(s):
    return s[0]

def snd(s):
    return s[1]

def inverseDict(d):
    """
    input d: a -> b
    output : b -> set(a)
    """
    return {
        v : set(imap(fst, kv_iter))
        for (v, kv_iter) in groupby(
            sorted(d.iteritems(),
                   key=snd),
            key=snd
        )
    }

En teoría, esto debería ser más rápido que agregar al conjunto (o agregar a la lista) uno por uno, como en la solución imperativa .

Desafortunadamente, los valores tienen que ser ordenables, la clasificación es requerida por groupby

cjay
fuente
1
"En teoría, esto debería ser más rápido que agregar al conjunto (o agregar a la lista) uno por uno" - no. Dados los nelementos en el dict original, su enfoque tiene O(n log n)complejidad de tiempo debido a la necesidad de clasificar los elementos del dict, mientras que el enfoque imperativo ingenuo tiene O(n)complejidad de tiempo. Por lo que sé, su enfoque puede ser más rápido hasta absurdamente grande dicten la práctica , pero ciertamente no es más rápido en teoría.
Mark Amery
0

Prueba esto para python 2.7 / 3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map
dhvlnyk
fuente
-1

Lo haría de esa manera en Python 2.

inv_map = {my_map[x] : x for x in my_map}
Genghiscrade
fuente
Iterar pares clave-valor simultáneamente a través de dict.items(o iteritemsen Python 2) es más eficiente que extraer cada valor por separado mientras se repiten las claves.
jpp
-1
def invertDictionary(d):
    myDict = {}
  for i in d:
     value = d.get(i)
     myDict.setdefault(value,[]).append(i)   
 return myDict
 print invertDictionary({'a':1, 'b':2, 'c':3 , 'd' : 1})

Esto proporcionará resultados como: {1: ['a', 'd'], 2: ['b'], 3: ['c']}

RVR
fuente
Iterar pares clave-valor simultáneamente a través de dict.items(o iteritemsen Python 2) es más eficiente que extraer cada valor por separado mientras se repiten las claves. Además, no ha agregado ninguna explicación a una respuesta que duplica a otras.
jpp
-1
  def reverse_dictionary(input_dict):
      out = {}
      for v in input_dict.values():  
          for value in v:
              if value not in out:
                  out[value.lower()] = []

      for i in input_dict:
          for j in out:
              if j in map (lambda x : x.lower(),input_dict[i]):
                  out[j].append(i.lower())
                  out[j].sort()
      return out

este código hace así:

r = reverse_dictionary({'Accurate': ['exact', 'precise'], 'exact': ['precise'], 'astute': ['Smart', 'clever'], 'smart': ['clever', 'bright', 'talented']})

print(r)

{'precise': ['accurate', 'exact'], 'clever': ['astute', 'smart'], 'talented': ['smart'], 'bright': ['smart'], 'exact': ['accurate'], 'smart': ['astute']}
Shb8086
fuente
1
En general, las respuestas son mucho más útiles si incluyen una explicación de lo que se pretende que haga el código y por qué eso resuelve el problema sin introducir otros.
Tom Aranda
1
Es muy agradable, pero muchas decisiones inexplicables (por ejemplo, ¿por qué minúsculas para las claves?)
Liudvikas Akelis
-2

No es algo completamente diferente, solo una receta un poco reescrita de Cookbook. Además, está optimizado por el setdefaultmétodo de retención , en lugar de hacerlo cada vez a través de la instancia:

def inverse(mapping):
    '''
    A function to inverse mapping, collecting keys with simillar values
    in list. Careful to retain original type and to be fast.
    >> d = dict(a=1, b=2, c=1, d=3, e=2, f=1, g=5, h=2)
    >> inverse(d)
    {1: ['f', 'c', 'a'], 2: ['h', 'b', 'e'], 3: ['d'], 5: ['g']}
    '''
    res = {}
    setdef = res.setdefault
    for key, value in mapping.items():
        setdef(value, []).append(key)
    return res if mapping.__class__==dict else mapping.__class__(res)

Diseñado para ejecutarse en CPython 3.x, para 2.x reemplazar mapping.items()conmapping.iteritems()

En mi máquina funciona un poco más rápido que otros ejemplos aquí

Thodnev
fuente
1
Construir el resultado como dictay luego convertirlo a la clase deseada al final (en lugar de comenzar con una clase del tipo correcto) me parece que incurre en un éxito de rendimiento completamente evitable, aquí.
Mark Amery
-2

Escribí esto con la ayuda del ciclo 'for' y el método '.get ()' y cambié el nombre 'map' del diccionario a 'map1' porque 'map' es una función.

def dict_invert(map1):
    inv_map = {} # new dictionary
    for key in map1.keys():
        inv_map[map1.get(key)] = key
    return inv_map
Taras Voitovych
fuente
-2

Si los valores no son únicos Y pueden ser un hash (una dimensión):

for k, v in myDict.items():
    if len(v) > 1:
        for item in v:
            invDict[item] = invDict.get(item, [])
            invDict[item].append(k)
    else:
        invDict[v] = invDict.get(v, [])
        invDict[v].append(k)

Y con una recursión si necesita profundizar, solo una dimensión:

def digList(lst):
    temp = []
    for item in lst:
        if type(item) is list:
            temp.append(digList(item))
        else:
            temp.append(item)
    return set(temp)

for k, v in myDict.items():
    if type(v) is list:
        items = digList(v)
        for item in items:
            invDict[item] = invDict.get(item, [])
            invDict[item].append(k)
    else:
        invDict[v] = invDict.get(v, [])
        invDict[v].append(k)
mveith
fuente
Puede mejorar sus soluciones utilizando un defaultdict: eliminará todas las líneas invDict [item] = invDict.get (item, [])
gabuzo
Su primer acercamiento que aquí se convierte {"foo": "bar"}a {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}y levanta una excepción si cualquier valor en myDictno es un iterable. No estoy seguro de qué comportamiento estaba tratando de implementar aquí, pero lo que realmente ha implementado es algo que nadie querrá.
Mark Amery