Odwróć / odwróć odwzorowanie słownika

663

Biorąc pod uwagę taki słownik:

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

Jak można odwrócić tę mapę, aby uzyskać:

inv_map = {1: 'a', 2: 'b'}
Brian M. Hunt
źródło

Odpowiedzi:

923

Dla Python 2.7.x

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

W przypadku Python 3+:

inv_map = {v: k for k, v in my_map.items()}
SilentGhost
źródło
4
W najnowszych wersjach Python 2.7.x również my_map.items()działa
valentin
29
Działa to z tym wyjątkiem, że nie zadziała, jeśli nie ma jedności w wartościach. W takim przypadku stracisz kilka wpisów
gabuzo
2
Tak, jako szczegół implementacji. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Nie ma gwarancji, że tak pozostanie, więc nie pisz kodu polegającego na Dicttakim samym zachowaniu jak OrderedDict.
Mattias
9
@Mattias, dotyczy to Pythona 3.6. W wersji 3.7 obsługa zamówień jest oficjalna: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL tak powiedział.
interDist
174

Zakładając, że wartości w dykcie są unikalne:

dict((v, k) for k, v in my_map.iteritems())
Rick wspiera Monikę
źródło
22
Wartości też muszą być haszowalne
John La Rooy
30
@ Buttons840: Jeśli wartości nie są unikalne, i tak nie występuje unikatowa inwersja słownika lub, innymi słowy, odwracanie nie ma sensu.
Wrzlprmft
2
@ Buttons840 Pojawi się tylko ostatni klucz wartości. Prawdopodobnie nie ma żadnych gwarancji na kolejność, która iteritems()zostanie wyprowadzona, więc można założyć, że arbitralny klucz zostanie przypisany do wartości nieunikalnej, w sposób, który będzie w oczywisty sposób odtwarzalny w pewnych warunkach, ale ogólnie nie.
Evgeni Sergeev
2
Zauważ oczywiście, że w Pythonie 3 nie ma już iteritems()metody i takie podejście nie zadziała; użyj items()tam zamiast tego, jak pokazano w zaakceptowanej odpowiedzi. Również tłumaczenie ze słownika uczyniłoby to ładniejszym niż dzwonienie dict.
Mark Amery
5
@Wrzlprmft Istnieje naturalna definicja odwrotności w przypadku niejednorodnych wartości. Każda wartość jest odwzorowana na zestaw kluczy, które do niej prowadzą.
Leo
135

Jeśli wartości w my_mapnie są unikalne:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)
Robert Rossney
źródło
56
... lub po prostu inv_map.setdefault (v, []). append (k). Kiedyś byłem fanem defaultdict, ale potem jeden raz mnie pieprzyłem i doszedłem do wniosku, że tak naprawdę jawne jest lepsze niż niejawne.
alsuren,
Ta odpowiedź jest niepoprawna dla wielu map, dołącz tutaj jest bezużyteczna, ponieważ wartość jest resetowana do pustej listy za każdym razem, należy użyć set_default
Jarosław
1
@YaroslavBulatov nie, pokazany tutaj kod nie jest uszkodzony - inv_map.get(v, [])zwraca listę, która już została dodana, jeśli taka istnieje, więc przypisanie nie resetuje się do pustej listy. setdefaultbyłby jednak ładniejszy.
Mark Amery
10
Zestaw miałby tutaj większy sens. Klucze są (prawdopodobnie) haszowalne i nie ma porządku. inv_map.setdefault(v, set()).add(k).
Artyer,
1
W python3 użyj my_map.items()zamiast my_map.iteritems().
apitsch
42

Aby to zrobić, zachowując typ mapowania (zakładając, że jest dictto dictpodklasa lub podklasa):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))
fs.
źródło
4
Być może jest to mądre, ale nie działa, gdy więcej niż jeden klucz ma tę samą wartość w oryginalnym słowniku.
Rafael_Espericueta
1
@Rafael_Espericueta To prawda o każdej możliwej odpowiedzi na to pytanie, ponieważ mapa z powtarzanymi wartościami nie jest odwracalna.
Mark Amery
2
@ Mark_Amery W pewnym sensie może być odwracalny bardziej ogólnie. Na przykład: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D jest słownikiem na przykład {rodzic: dzieci}, podczas gdy Dinv jest słownikiem {dziecko: rodzice}.
Rafael_Espericueta
36

Spróbuj tego:

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

(Zauważ, że dokumenty Pythona dotyczące widoków słownika wyraźnie to gwarantują .keys()i .values()mają ich elementy w tej samej kolejności, co pozwala na działanie powyższego podejścia.)

Alternatywnie:

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

lub używając rozumienia słownika w Pythonie 3.0

inv_map = {my_map[k] : k for k in my_map}
sykora
źródło
1
Zauważ, że działa to tylko wtedy, gdy klucze są unikalne (co prawie nigdy nie ma miejsca, jeśli chcesz je odwrócić).
gented
Według python.org/dev/peps/pep-0274, rozumienia dict są również dostępne w wersji 2.7+.
Kawu
24

Kolejny, bardziej funkcjonalny sposób:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Brendan Maguire
źródło
3
Dzięki za wysłanie. Nie jestem pewien, czy jest to lepsze - zacytować Guido Van Rossuma w PEP 279: „ filteri mappowinien umrzeć i zostać pochłonięty listami, a nie wyhodować więcej wariantów”.
Brian M. Hunt
2
Tak, to słuszna uwaga Brian. Właśnie dodałem to jako punkt rozmowy. Sposób rozumienia słownika jest bardziej czytelny dla większości, jakie mogłem sobie wyobrazić. (I pewnie też szybsze, jak sądzę)
Brendan Maguire
3
Może być mniej czytelny niż inne, ale w ten sposób można wymienić dictinne typy mapowania, takie jak collections.OrderedDictlubcollections.defaultdict
Czy S
10

To rozszerza się na odpowiedź Roberta , mającą zastosowanie do sytuacji, gdy wartości w dykcie nie są unikalne.

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

Implementacja jest ograniczona, ponieważ nie można użyć reverseddwa razy i odzyskać oryginał. To nie jest symetryczne jako takie. Jest testowany z Python 2.6. Oto przykład użycia tego, w jaki sposób drukuję wynikowy dykt.

Jeśli wolisz użyć setniż a list, a mogą istnieć nieuporządkowane aplikacje, dla których ma to sens, zamiast setdefault(v, []).append(k)używać setdefault(v, set()).add(k).

Acumenus
źródło
byłoby to również dobre miejsce do używania zestawów zamiast list, tj.revdict.setdefault(v, set()).add(k)
mueslo,
Oczywiście, ale właśnie dlatego jest to dobry powód do korzystania set. Obowiązuje tutaj typ wewnętrzny. Co jeśli chcę znaleźć wszystkie klucze, których wartości nie są 1lub 2? Potem mogę po prostu zrobić d.keys() - inv_d[1] - inv_d[2](w Pythonie 3)
mueslo
9

Możemy również odwrócić słownik za pomocą duplikatów kluczy, używając 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']}  

Zobacz tutaj :

Ta technika jest prostsza i szybsza niż technika równoważna dict.setdefault() .

irudyak
źródło
6

Na przykład masz następujący słownik:

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

I chcesz uzyskać to w takiej odwróconej formie:

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

Pierwsze rozwiązanie . Aby odwrócić pary klucz-wartość w słowniku, użyj formetody -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)

Drugie rozwiązanie . Użyj inwersji ze słownikiem :

# Use this code to invert dictionaries that have unique values

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

Trzecie rozwiązanie . Użyj odwrócenia metody inwersji (polega na drugim rozwiązaniu):

# 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
źródło
4
dictjest zarezerwowany i nie należy go używać do nazw zmiennych
crypdick
2
zapomniałem powiedzieć, co to my_mapjest
crypdick
dictio()? Miałeś na myśli dict()?
Georgy
5

Połączenie rozumienia listy i słownika. Może obsługiwać duplikaty kluczy

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}
SVJ
źródło
1
Podobnie jak stackoverflow.com/a/41861007/1709587 , jest to rozwiązanie O (n²) dla problemu, który można łatwo rozwiązać w O (n) za pomocą kilku dodatkowych wierszy kodu.
Mark Amery
2

Jeśli wartości nie są unikalne, a ty jesteś trochę hardkorowy:

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

Zwłaszcza w przypadku dużego nagrania zauważ, że to rozwiązanie jest o wiele mniej wydajne niż odpowiedź Python odwracający / odwracający mapowanie, ponieważ zapętla się items()wielokrotnie.

pcv
źródło
7
Jest to po prostu nieczytelny i dobry przykład, jak nie pisać kodu, który można utrzymać. Nie zrobię tego, -1ponieważ nadal odpowiada na pytanie, tylko moja opinia.
Russ Bradberry
1

Oprócz innych funkcji sugerowanych powyżej, jeśli lubisz lambdas:

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

Możesz też zrobić to w ten sposób:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
źródło
2
-1; wszystko, co zrobiłeś, bierze inne odpowiedzi ze strony i umieszcza je w lambda. Również przypisanie lambda do zmiennej stanowi naruszenie PEP 8 .
Mark Amery
1

Myślę, że najlepszym sposobem na to jest zdefiniowanie klasy. Oto implementacja „słownika symetrycznego”:

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

Metody usuwania i iteracji są wystarczająco łatwe do wdrożenia, jeśli są potrzebne.

Ta implementacja jest znacznie wydajniejsza niż odwracanie całego słownika (który wydaje się być najpopularniejszym rozwiązaniem na tej stronie). Nie wspominając o tym, że możesz dodawać lub usuwać wartości z SymDict tyle, ile chcesz, a słownik odwrotny zawsze pozostanie ważny - nie jest to prawdą, jeśli po prostu raz odwrócisz cały słownik.

NcAdams
źródło
Podoba mi się ten pomysł, choć dobrze byłoby zauważyć, że wymienia dodatkową pamięć w celu uzyskania lepszych obliczeń. Szczęśliwszym medium może być buforowanie lub leniwe obliczanie lustra. Warto również zauważyć, że można by uczynić go bardziej syntaktycznym dzięki np. Widokom słowników i operatorom niestandardowym.
Brian M. Hunt
@ BrianM.Hunt Wymienia pamięć, ale niewiele. Przechowujesz tylko dwa zestawy wskaźników dla każdego obiektu. Jeśli twoje obiekty są znacznie większe niż pojedyncze liczby całkowite, nie zrobi to dużej różnicy. Z drugiej strony, jeśli masz ogromny stół z drobnymi przedmiotami, być może będziesz musiał rozważyć te sugestie ...
NcAdams
I zgadzam się, że jest jeszcze wiele do zrobienia - mógłbym to później przekształcić w pełni działający typ danych
NcAdams
2
„Ta implementacja jest znacznie wydajniejsza niż odwracanie całego słownika” - um, dlaczego? Nie widzę żadnego wiarygodnego sposobu, w jaki to podejście może przynieść znaczące korzyści w zakresie wydajności; nadal masz w ten sposób dwa słowniki. Jeśli cokolwiek, spodziewam się, że będzie to wolniejsze niż, powiedzmy, odwrócenie dykta ze zrozumieniem, ponieważ jeśli odwrócisz dykta, Python może z góry wiedzieć, ile wiader do przydzielenia w podstawowej strukturze danych C i utworzyć odwrotną mapę bez dzwonienia dictresize, ale takie podejście zaprzecza Pythonowi na taką możliwość.
Mark Amery
1

To obsługuje nie unikalne wartości i zachowuje wiele z wyglądu unikalnej skrzynki.

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

Dla Pythona 3.x, wymienić itervaluesz values.

Ersatz Kwisatz
źródło
3
To rozwiązanie jest dość eleganckie jako jedna podszewka i zarządza przypadkiem wartości niepowtarzalnych. Ma jednak złożoność w O (n2), co oznacza, że ​​powinno być w porządku dla kilkudziesięciu elementów, ale byłoby zbyt wolne do praktycznego zastosowania, jeśli masz kilkaset tysięcy elementów w początkowym słowniku. Rozwiązania oparte na domyślnym dykcie są znacznie szybsze od tego.
gabuzo
Gabuzo ma rację. Ta wersja jest (prawdopodobnie) jaśniejsza niż niektóre, ale nie nadaje się do dużych danych.
Ersatz Kwisatz
0

Funkcja jest symetryczna dla wartości listy typów; Krotki są pokrywane do list podczas wykonywania reverse_dict (reverse_dict (słownik))

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
źródło
0

Ponieważ słowniki wymagają jednego unikalnego klucza w słowniku, w przeciwieństwie do wartości, musimy dołączyć odwrócone wartości do listy sortowania, która ma zostać uwzględniona w nowych określonych kluczach.

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
źródło
0

Szybkie funkcjonalne rozwiązanie dla map niebiotywnych (wartości nie unikalne):

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

Teoretycznie powinno to być szybsze niż dodawanie do zestawu (lub dołączanie do listy) jeden po drugim, jak w rozwiązaniu imperatywnym .

Niestety wartości muszą być sortowalne, sortowanie jest wymagane przez groupby.

cjay
źródło
1
„Teoretycznie powinno to być szybsze niż dodawanie do zestawu (lub dołączanie do listy) jeden po drugim” - nie. Biorąc pod uwagę nelementy oryginalnego dyktanda, twoje podejście ma O(n log n)złożoność czasową z powodu potrzeby sortowania elementów tego dykta, podczas gdy naiwne imperatywne podejście ma O(n)złożoność czasową. Z tego co wiem, twoje podejście może być szybsze aż do absurdalnie dużych dictw praktyce , ale z pewnością nie jest szybsze w teorii.
Mark Amery
0

Wypróbuj to dla Pythona 2.7 / 3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map
dhvlnyk
źródło
-1

Zrobiłbym tak w Pythonie 2.

inv_map = {my_map[x] : x for x in my_map}
genghiscrade
źródło
Iterowanie par klucz-wartość jednocześnie przez dict.items(lub iteritemsw Pythonie 2) jest bardziej wydajne niż wyodrębnianie każdej wartości osobno podczas iteracji kluczy.
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})

Zapewni to wynik w postaci: {1: ['a', 'd'], 2: ['b'], 3: ['c']}

RVR
źródło
Iterowanie par klucz-wartość jednocześnie przez dict.items(lub iteritemsw Pythonie 2) jest bardziej wydajne niż wyodrębnianie każdej wartości osobno podczas iteracji kluczy. Ponadto nie dodałeś żadnego wyjaśnienia do odpowiedzi, która powiela inne osoby.
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

ten kod robi to tak:

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
źródło
1
Zasadniczo odpowiedzi są o wiele bardziej pomocne, jeśli zawierają wyjaśnienie, do czego służy kod i dlaczego rozwiązuje problem bez przedstawiania innych.
Tom Aranda,
1
To bardzo miłe, ale wiele niewyjaśnionych decyzji (na przykład dlaczego małe litery na klucze?)
Liudvikas Akelis
-2

Nie jest to coś zupełnie innego, tylko nieco przepisany przepis z książki kucharskiej. Jest dalej optymalizowany metodą przechowywania setdefault, zamiast za każdym razem, gdy przechodzi przez instancję:

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)

Zaprojektowany do pracy pod CPython 3.x, dla wersji 2.x mapping.items() zmapping.iteritems()

Na mojej maszynie działa trochę szybciej niż inne przykłady tutaj

Thodnev
źródło
1
Budowanie wyniku jako dicta następnie przekształcanie go w pożądaną klasę na końcu (zamiast zaczynać od klasy odpowiedniego typu) wydaje mi się, że powoduje tutaj całkowicie niemożliwe do uniknięcia uderzenie wydajności.
Mark Amery
-2

Napisałem to za pomocą cyklu „for” i metody „.get ()” i zmieniłem nazwę „map” słownika na „map1”, ponieważ „map” jest funkcją.

def dict_invert(map1):
    inv_map = {} # new dictionary
    for key in map1.keys():
        inv_map[map1.get(key)] = key
    return inv_map
Taras Voitovych
źródło
-2

Jeśli wartości nie są unikalne ORAZ może być skrótem (jeden wymiar):

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)

I z rekurencją, jeśli chcesz kopać głębiej, to tylko jeden wymiar:

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
źródło
Możesz ulepszyć swoje rozwiązania za pomocą defaultdict: usunie wszystkie wiersze invDict [item] = invDict.get (item, [])
gabuzo
Twoje pierwsze podejście tutaj przekształca {"foo": "bar"}się {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}i podnosi wyjątek jeśli wartość myDictnie jest iterable. Nie jestem pewien, jakie zachowanie próbujesz tutaj zastosować, ale to, co faktycznie wdrożyłeś, jest czymś, czego nikt nie będzie chciał.
Mark Amery