Invertire / invertire una mappatura del dizionario


Risposte:


924

Per Python 2.7.x

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

Per Python 3+:

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

4
Nelle recenti versioni di Python 2.7.x my_map.items()funziona anche
valentin

30
Funzionerà tranne che non funzionerà se non c'è unicità nei valori. In tal caso perderai alcune voci
gabuzo,


2
Sì, come dettaglio di implementazione. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Non vi è alcuna garanzia che rimarrà tale, quindi non scrivere codice facendo affidamento sullo Dictstesso comportamento di OrderedDict.
Mattias,

9
@Mattias, questo è vero per Python 3.6. Per la versione 3.7 la conservazione degli ordini è ufficiale: mail.python.org/pipermail/python-dev/2017-December/151283.html . Lo ha detto BDFL.
interDist,

174

Supponendo che i valori nel dict siano univoci:

dict((v, k) for k, v in my_map.iteritems())

22
Anche i valori devono essere hash
John La Rooy,

30
@ Buttons840: se i valori non sono univoci, non esiste comunque un'inversione univoca del dizionario o, in altre parole, l'inversione non ha senso.
Wrzlprmft,

2
@ Buttons840 Verrà visualizzata solo l'ultima chiave per il valore. Probabilmente non ci sono garanzie sull'ordine che iteritems()verrà emesso, quindi si può presumere che una chiave arbitraria verrà assegnata per un valore non univoco, in un modo che apparentemente sarà riproducibile in alcune condizioni, ma non così in generale.
Evgeni Sergeev,

2
Nota, ovviamente, che in Python 3 non esiste più un iteritems()metodo e questo approccio non funzionerà; usalo items()invece come mostrato nella risposta accettata. Inoltre, una comprensione del dizionario renderebbe questo più bello del chiamare dict.
Mark Amery,

5
@Wrzlprmft Esiste una definizione naturale per inversa nel caso di valori non univoci. Ogni valore è associato al set di chiavi che lo porta.
Leone,

135

Se i valori in my_mapnon sono univoci:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)

56
... o semplicemente inv_map.setdefault (v, []). append (k). Ero un fanboy di default, ma poi sono stato fregato troppe volte e ho concluso che in realtà esplicito è meglio di implicito.
alsuren,

Questa risposta non è corretta per la mappa multipla, l'appendice qui è inutile perché il valore viene reimpostato nella lista vuota ogni volta, dovrebbe usare set_default
Yaroslav Bulatov

1
@YaroslavBulatov no, il codice come mostrato qui non è rotto - inv_map.get(v, [])restituisce l'elenco già aggiunto se ce n'è uno, quindi l'assegnazione non viene ripristinata su un elenco vuoto. setdefaultsarebbe comunque più bello, comunque.
Mark Amery,

10
Un set avrebbe più senso qui. Le chiavi sono (probabilmente) cancellabili e non c'è ordine. inv_map.setdefault(v, set()).add(k).
Artyer,

1
In python3, utilizzare my_map.items()invece di my_map.iteritems().
apitsch,

42

Per fare ciò preservando il tipo di mappatura (supponendo che si tratti di una dicto una dictsottoclasse):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))

4
Può essere intelligente, ma non funziona quando più di una chiave ha lo stesso valore nel dizionario originale.
Rafael_Espericueta,

1
@Rafael_Espericueta Questo è vero per ogni possibile risposta a questa domanda, poiché una mappa con valori ripetuti non è invertibile.
Mark Amery,

2
@Mark_Amery Può essere invertibile più in generale, in un certo senso. Ad esempio: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D è un dizionario ad esempio {parent: children}, mentre Dinv è il dizionario {child: parent}.
Rafael_Espericueta,

36

Prova questo:

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

(Si noti che i documenti Python nelle viste del dizionario garantiscono esplicitamente che .keys()e .values()hanno i loro elementi nello stesso ordine, il che consente al metodo sopra di funzionare).

In alternativa:

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

o usando la comprensione del dict di python 3.0

inv_map = {my_map[k] : k for k in my_map}

1
Si noti che questo funziona solo se le chiavi sono uniche (il che non è quasi mai il caso se si desidera invertirle).
gented

Secondo python.org/dev/peps/pep-0274 la comprensione del dict è disponibile anche in 2.7+.
Kawu,

24

Un altro modo più funzionale:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))

3
Grazie per la pubblicazione. Non sono sicuro che sia preferibile - per citare Guido Van Rossum nel PEP 279: " filtere mapdovrei morire ed essere riassunto nella comprensione dell'elenco, non far crescere altre varianti".
Brian M. Hunt,

2
Sì, è un bel punto Brian. Lo stavo solo aggiungendo come punto di conversazione. Il modo di comprensione del dettato è più leggibile per la maggior parte che immagino. (E probabilmente anche più veloce immagino)
Brendan Maguire,

3
Potrebbe essere meno leggibile di altri, ma in questo modo ha il vantaggio di essere in grado di scambiare dictcon altri tipi di mappatura come collections.OrderedDictocollections.defaultdict
Will S

10

Questo si espande sulla risposta di Robert , applicando a quando i valori nel dict non sono unici.

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

L'implementazione è limitata in quanto non è possibile utilizzare reverseddue volte e recuperare l'originale. Non è simmetrico in quanto tale. È testato con Python 2.6. Ecco un caso d'uso di come sto usando per stampare il dict risultante.

Se preferisci usare a piuttosto setche a list, e potrebbero esistere applicazioni non ordinate per cui ha senso, invece di setdefault(v, []).append(k), usare setdefault(v, set()).add(k).


questo sarebbe anche un buon posto per usare i set invece delle liste, cioèrevdict.setdefault(v, set()).add(k)
mueslo

Certo, ma questo è esattamente il motivo per cui è un buon motivo per usarlo set. È il tipo intrinseco che si applica qui. Cosa succede se desidero trovare tutte le chiavi in ​​cui i valori non sono 1o 2? Allora posso solo fare d.keys() - inv_d[1] - inv_d[2](in Python 3)
mueslo il

9

Possiamo anche invertire un dizionario con chiavi duplicate 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']}  

Vedi qui :

Questa tecnica è più semplice e veloce di una tecnica equivalente che utilizza dict.setdefault().


6

Ad esempio, hai il seguente dizionario:

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

E vuoi prenderlo in una forma così invertita:

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

Prima soluzione . Per invertire le coppie chiave-valore nel dizionario utilizzare un forapproccio -loop:

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

Seconda soluzione . Utilizzare un approccio di comprensione del dizionario per l'inversione:

# Use this code to invert dictionaries that have unique values

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

Terza soluzione . Utilizzare il ripristino dell'approccio di inversione (si basa sulla seconda soluzione):

# 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]}

4
dictè riservato e non dovrebbe essere usato per i nomi delle variabili
crypdick il

2
dimenticato di dirci che cosa my_mapè
crypdick

dictio()? Volevi dire dict()?
Georgy,

5

Combinazione di elenco e comprensione del dizionario. Può gestire chiavi duplicate

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}

1
Come stackoverflow.com/a/41861007/1709587 , questa è una soluzione O (n²) a un problema che può essere facilmente risolto in O (n) con un paio di righe di codice aggiuntive.
Mark Amery,

2

Se i valori non sono unici e sei un po 'hardcore:

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

Soprattutto per un dict di grandi dimensioni, si noti che questa soluzione è molto meno efficiente della risposta Python inverte / inverte una mappatura perché scorre items()più volte.


7
Questo è semplicemente illeggibile e un buon esempio di come non scrivere codice gestibile. Non lo farò -1perché risponde ancora alla domanda, solo la mia opinione.
Russ Bradberry,

1

Oltre alle altre funzioni suggerite sopra, se ti piacciono le lambda:

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

Oppure potresti farlo anche in questo modo:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )

2
-1; tutto ciò che hai fatto è prendere altre risposte dalla pagina e metterle in un lambda. Inoltre, assegnare una lambda a una variabile è una violazione di PEP 8 .
Mark Amery,

1

Penso che il modo migliore per farlo sia definire una classe. Ecco un'implementazione di un "dizionario simmetrico":

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

I metodi di eliminazione e iterazione sono abbastanza facili da implementare se necessari.

Questa implementazione è molto più efficiente dell'inversione di un intero dizionario (che sembra essere la soluzione più popolare in questa pagina). Per non parlare, puoi aggiungere o rimuovere valori dal tuo SymDict quanto vuoi e il tuo dizionario inverso rimarrà sempre valido - questo non è vero se si inverte semplicemente l'intero dizionario una volta.


Mi piace questa idea, anche se sarebbe bene notare che scambia memoria aggiuntiva per ottenere un calcolo migliore. Un supporto più felice potrebbe essere la memorizzazione nella cache o il calcolo pigramente dello specchio. Vale anche la pena notare che potrebbe essere reso più sintatticamente accattivante, ad esempio con visualizzazioni di dizionario e operatori personalizzati.
Brian M. Hunt,

@ BrianM.Hunt Scambia la memoria, ma non molto. Si memorizzano solo due set di puntatori per ciascun oggetto. Se i tuoi oggetti sono molto più grandi dei singoli numeri interi, questo non farà molta differenza. Se hai una grande tavola di piccoli oggetti d'altra parte, potresti dover considerare quei suggerimenti ...
NcAdams,

E sono d'accordo, c'è molto altro da fare qui - potrei approfondire questo in un tipo di dati pienamente funzionante in seguito
NcAdams,

2
"Questa implementazione è molto più efficiente dell'inversione di un intero dizionario" - um, perché? Non vedo alcun modo plausibile che questo approccio possa avere un significativo vantaggio in termini di prestazioni; hai ancora due dizionari in questo modo. Semmai, mi aspetto che questo sia più lento di, diciamo, invertendo il dict con una comprensione, perché se si inverte il dict, Python può plausibilmente sapere in anticipo quanti bucket allocare nella struttura di dati C sottostante e creare la mappa inversa senza mai chiamare dictresize, ma questo approccio nega a Python questa possibilità.
Mark Amery,

1

Gestisce valori non univoci e mantiene gran parte dell'aspetto del caso unico.

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

Per Python 3.x, sostituire itervaluescon values.


3
Questa soluzione è abbastanza elegante come una fodera e gestisce il caso di valori non unici. Tuttavia ha una complessità in O (n2), il che significa che dovrebbe andare bene per diverse dozzine di elementi, ma sarebbe troppo lento per un uso pratico se nel tuo dizionario iniziale ci sono diverse centinaia di migliaia di elementi. Le soluzioni basate su un dict predefinito sono molto più veloci di questa.
gabuzo,

Gabuzo ha ragione. Questa versione è (probabilmente) più chiara di alcune, ma non è adatta per dati di grandi dimensioni.
Ersatz Kwisatz,

0

La funzione è simmetrica per i valori dell'elenco dei tipi; Le tuple vengono convertite in elenchi quando si esegue reverse_dict (reverse_dict (dizionario))

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

0

Poiché i dizionari richiedono una chiave univoca all'interno del dizionario a differenza dei valori, dobbiamo aggiungere i valori invertiti in un elenco di ordinamento da includere nelle nuove chiavi specifiche.

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

0

Soluzione funzionale veloce per mappe non biiettive (valori non univoci):

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

In teoria questo dovrebbe essere più veloce dell'aggiunta al set (o aggiunta alla lista) uno alla volta come nella soluzione imperativa .

Purtroppo i valori devono essere ordinabili, l'ordinamento è richiesto da groupby.


1
"In teoria questo dovrebbe essere più veloce dell'aggiunta al set (o aggiunta all'elenco) uno per uno" - no. Dati gli nelementi del dict originale, il tuo approccio ha una O(n log n)complessità temporale dovuta alla necessità di ordinare gli articoli del dict, mentre l'approccio ingenuo imperativo ha una O(n)complessità temporale. Per quanto ne so, il tuo approccio potrebbe essere più veloce fino a quando dictin pratica è assurdamente grande , ma di certo non è più veloce in teoria.
Mark Amery,

0

Prova questo per Python 2.7 / 3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map

-1

Lo farei così in Python 2.

inv_map = {my_map[x] : x for x in my_map}

L'iterazione simultanea di coppie chiave-valore tramite dict.items(o iteritemsin Python 2) è più efficiente dell'estrazione di ciascun valore separatamente durante l'iterazione delle chiavi.
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})

Ciò fornirà un output come: {1: ['a', 'd'], 2: ['b'], 3: ['c']}


L'iterazione simultanea di coppie chiave-valore tramite dict.items(o iteritemsin Python 2) è più efficiente dell'estrazione di ciascun valore separatamente durante l'iterazione delle chiavi. Inoltre, non hai aggiunto alcuna spiegazione a una risposta che duplica gli altri.
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

questo codice fa così:

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

1
In generale, le risposte sono molto più utili se includono una spiegazione di ciò che il codice è destinato a fare e perché risolve il problema senza introdurre altri.
Tom Aranda,

1
È molto bello, ma molte decisioni inspiegabili (ad esempio, perché minuscole per le chiavi?)
Liudvikas Akelis

-2

Non qualcosa di completamente diverso, solo una ricetta un po 'riscritta da Cookbook. Inoltre è ottimizzato mantenendo il setdefaultmetodo, anziché ogni volta che lo ottiene attraverso l'istanza:

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)

Progettato per essere eseguito con CPython 3.x, per 2.x sostituirlo mapping.items()conmapping.iteritems()

Sulla mia macchina funziona un po 'più veloce rispetto ad altri esempi qui


1
Costruire il risultato come un dicte poi convertirlo alla classe desiderata alla fine (piuttosto che iniziare con una classe del tipo giusto) mi sembra che incorre in un colpo di prestazione del tutto evitabile, qui.
Mark Amery,

-2

Ho scritto questo con l'aiuto del ciclo 'for' e del metodo '.get ()' e ho cambiato il nome 'map' del dizionario in 'map1' perché 'map' è una funzione.

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

-2

Se i valori non sono univoci AND può essere un hash (una dimensione):

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)

E con una ricorsione se hai bisogno di scavare più a fondo allora solo una dimensione:

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)

Potresti migliorare le tue soluzioni usando un defaultdict: rimuoverà tutte le righe invDict [item] = invDict.get (item, [])
gabuzo

Il tuo primo approccio qui converte {"foo": "bar"}per {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}e solleva un'eccezione se qualsiasi valore myDictnon è un iterabile. Non sono sicuro del comportamento che stavi cercando di implementare qui, ma ciò che hai effettivamente implementato è qualcosa che praticamente nessuno vorrebbe.
Mark Amery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.