Inverser / inverser un mappage de dictionnaire

663

Étant donné un dictionnaire comme celui-ci:

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

Comment inverser cette carte pour obtenir:

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

Réponses:

924

Pour Python 2.7.x

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

Pour Python 3+:

inv_map = {v: k for k, v in my_map.items()}
SilentGhost
la source
4
Dans les dernières versions de Python 2.7.x, ça my_map.items()marche aussi
Valentin
30
Cela fonctionnera, sauf que cela ne fonctionnera pas s'il n'y a pas d'unicité dans les valeurs. Dans ce cas, vous perdrez certaines entrées
gabuzo
2
Oui, comme détail d'implémentation. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Il n'y a aucune garantie que cela restera ainsi, donc n'écrivez pas de code en vous basant sur Dictle même comportement que OrderedDict.
Mattias
9
@Mattias, cela est vrai pour Python 3.6. Pour la version 3.7, la conservation des commandes est officielle: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL l'a dit.
interDist
174

En supposant que les valeurs du dict sont uniques:

dict((v, k) for k, v in my_map.iteritems())
Rick soutient Monica
la source
22
Les valeurs doivent également être lavables
John La Rooy
30
@ Buttons840: Si les valeurs ne sont pas uniques, il n'y a de toute façon pas d'inversion unique du dictionnaire ou, en d'autres termes, l'inversion n'a pas de sens.
Wrzlprmft
2
@ Buttons840 Seule la dernière touche apparaîtra pour la valeur. Il n'y a probablement aucune garantie sur l'ordre qui iteritems()sortira, donc on peut supposer qu'une clé arbitraire sera attribuée pour une valeur non unique, d'une manière qui sera apparemment reproductible dans certaines conditions, mais pas en général.
Evgeni Sergeev
2
Notez bien sûr qu'en Python 3 il n'y a plus de iteritems()méthode et que cette approche ne fonctionnera pas; utilisez items()-le à la place comme indiqué dans la réponse acceptée. De plus, une compréhension du dictionnaire rendrait cela plus joli que d'appeler dict.
Mark Amery
5
@Wrzlprmft Il existe une définition naturelle de l'inverse dans le cas de valeurs non uniques. Chaque valeur est mappée sur le jeu de clés qui y mène.
Leo
135

Si les valeurs de my_mapne sont pas uniques:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)
Robert Rossney
la source
56
... ou tout simplement inv_map.setdefault (v, []). append (k). J'étais un fanboy par défaut, mais je me suis fait visser une fois de plus et j'ai conclu qu'explicite est mieux qu'implicite.
alsuren
Cette réponse est incorrecte pour les multi-cartes, ajouter ici est inutile car la valeur est réinitialisée à la liste vide à chaque fois, doit utiliser set_default
Yaroslav Bulatov
1
@YaroslavBulatov non, le code tel qu'illustré ici n'est pas cassé - inv_map.get(v, [])renvoie la liste déjà ajoutée s'il y en a une, donc l'affectation ne se réinitialise pas dans une liste vide. setdefaultserait encore plus joli, cependant.
Mark Amery
10
Un ensemble aurait plus de sens ici. Les clés sont (probablement) lavables et il n'y a pas de commande. inv_map.setdefault(v, set()).add(k).
Artyer
1
En python3, utilisez my_map.items()au lieu de my_map.iteritems().
apitsch
42

Pour ce faire tout en préservant le type de votre mappage (en supposant qu'il s'agit d'une dictou d'une dictsous - classe):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))
fs.
la source
4
C'est peut-être intelligent, mais cela ne fonctionne pas lorsque plusieurs clés ont la même valeur dans le dictionnaire d'origine.
Rafael_Espericueta
1
@Rafael_Espericueta Cela est vrai de toute réponse possible à cette question, car une carte avec des valeurs répétées n'est pas inversible.
Mark Amery
2
@Mark_Amery Il peut être inversible plus généralement, dans un sens. Par exemple: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D est un dictionnaire de par exemple {parent: children}, tandis que Dinv est le dictionnaire {child: parents}.
Rafael_Espericueta
36

Essaye ça:

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

(Notez que les documents Python sur les vues de dictionnaire garantissent explicitement cela .keys()et .values()ont leurs éléments dans le même ordre, ce qui permet à l'approche ci-dessus de fonctionner.)

Alternativement:

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

ou en utilisant les compréhensions dict de python 3.0

inv_map = {my_map[k] : k for k in my_map}
sykora
la source
1
Notez que cela ne fonctionne que si les clés sont uniques (ce qui n'est presque jamais le cas si vous souhaitez les inverser).
gented
Selon python.org/dev/peps/pep-0274, les compréhensions dict sont également disponibles en 2.7+.
Kawu
24

Une autre façon, plus fonctionnelle:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Brendan Maguire
la source
3
Merci d'avoir posté. Je ne suis pas sûr que ce soit préférable - pour citer Guido Van Rossum dans PEP 279: " filteret mapdevrait mourir et être inclus dans des listes de compréhension, ne pas développer plus de variantes".
Brian M. Hunt
2
Oui, c'est un bon point Brian. Je ne faisais que l'ajouter comme point de conversation. La façon de comprendre le dict est plus lisible pour la plupart que j'imagine. (Et probablement plus vite aussi, je suppose)
Brendan Maguire
3
Peut être moins lisible que d'autres, mais cette façon a l'avantage de pouvoir être échangée dictavec d'autres types de mappage tels que collections.OrderedDictoucollections.defaultdict
Will S
10

Cela étend la réponse de Robert , s'appliquant lorsque les valeurs du dict ne sont pas uniques.

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'implémentation est limitée en ce sens que vous ne pouvez pas l'utiliser reverseddeux fois et récupérer l'original. Il n'est pas symétrique en tant que tel. Il est testé avec Python 2.6. Voici un cas d'utilisation de la façon dont j'utilise pour imprimer le dict résultant.

Si vous préférez utiliser un setplutôt qu'un list, et il pourrait exister des applications non ordonnées pour lesquelles cela a du sens, au lieu de setdefault(v, []).append(k), utiliser setdefault(v, set()).add(k).

Acumenus
la source
ce serait aussi un bon endroit pour utiliser des ensembles au lieu de listes, c'estrevdict.setdefault(v, set()).add(k)
mueslo
Bien sûr, mais c'est exactement pourquoi c'est une bonne raison d'utiliser set. C'est le type intrinsèque qui s'applique ici. Et si je veux trouver toutes les clés où les valeurs ne sont pas 1ou 2? Ensuite, je peux simplement faire d.keys() - inv_d[1] - inv_d[2](en Python 3)
mueslo
9

Nous pouvons également inverser un dictionnaire avec des clés en double en utilisant 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']}  

Voir ici :

Cette technique est plus simple et plus rapide qu'une technique équivalente utilisant dict.setdefault().

irudyak
la source
6

Par exemple, vous disposez du dictionnaire suivant:

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

Et vous voulez l'obtenir sous une telle forme inversée:

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

Première solution . Pour inverser les paires clé-valeur dans votre dictionnaire, utilisez une forapproche -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)

Deuxième solution . Utilisez une approche de compréhension de dictionnaire pour l'inversion:

# Use this code to invert dictionaries that have unique values

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

Troisième solution . Utilisation de l'inversion de l' approche d' inversion (repose sur la deuxième solution):

# 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
la source
4
dictest réservé et ne doit pas être utilisé pour les noms de variables
crypdick
2
oublié de nous dire ce qui my_mapest
crypdick
dictio()? Vous vouliez dire dict()?
Georgy
5

Combinaison de la liste et de la compréhension du dictionnaire. Peut gérer les clés en double

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}
SVJ
la source
1
Comme stackoverflow.com/a/41861007/1709587 , il s'agit d'une solution O (n²) à un problème qui est facilement résolu dans O (n) avec quelques lignes de code supplémentaires.
Mark Amery
2

Si les valeurs ne sont pas uniques et que vous êtes un peu 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())
)

Surtout pour un dict large, notez que cette solution est beaucoup moins efficace que la réponse Python inverser / inverser un mappage car il boucle items()plusieurs fois.

pcv
la source
7
C'est tout simplement illisible et un bon exemple de la façon de ne pas écrire de code maintenable. Je ne le ferai pas -1car cela répond toujours à la question, juste mon opinion.
Russ Bradberry du
1

En plus des autres fonctions suggérées ci-dessus, si vous aimez les lambdas:

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

Ou, vous pouvez aussi le faire de cette façon:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
la source
2
-1; tout ce que vous avez fait est de prendre d'autres réponses de la page et de les mettre dans un lambda. En outre, l'attribution d'un lambda à une variable est une violation de PEP 8 .
Mark Amery
1

Je pense que la meilleure façon de le faire est de définir une classe. Voici une implémentation d'un "dictionnaire symétrique":

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

Les méthodes de suppression et d'itération sont assez faciles à mettre en œuvre si elles sont nécessaires.

Cette implémentation est bien plus efficace que d'inverser un dictionnaire entier (ce qui semble être la solution la plus populaire sur cette page). Sans oublier, vous pouvez ajouter ou supprimer des valeurs de votre SymDict autant que vous le souhaitez, et votre dictionnaire inverse restera toujours valide - ce n'est pas vrai si vous inversez simplement le dictionnaire entier une fois.

NcAdams
la source
J'aime cette idée, mais il serait bon de noter qu'elle échange de la mémoire supplémentaire pour obtenir un calcul amélioré. Un support plus heureux peut être la mise en cache ou le calcul paresseux du miroir. Il convient également de noter qu'il pourrait être rendu plus attrayant sur le plan syntaxique avec par exemple des vues de dictionnaire et des opérateurs personnalisés.
Brian M. Hunt du
@ BrianM.Hunt Il échange la mémoire, mais pas beaucoup. Vous ne stockez que deux ensembles de pointeurs sur chaque objet. Si vos objets sont beaucoup plus grands que des entiers simples, cela ne fera pas beaucoup de différence. Si vous avez une énorme table de minuscules objets d'autre part, vous devrez peut-être considérer ces suggestions ...
NcAdams
Et je suis d'accord, il y a plus à faire ici - je pourrais étoffer cela dans un type de données pleinement fonctionnel plus tard
NcAdams
2
"Cette implémentation est bien plus efficace que d'inverser un dictionnaire entier" - euh, pourquoi? Je ne vois aucun moyen plausible que cette approche puisse avoir un avantage significatif en termes de performances; vous avez toujours deux dictionnaires de cette façon. Si quoi que ce soit, je m'attendrais à ce que ce soit plus lent que, disons, inverser le dict avec une compréhension, parce que si vous inversez le dict, Python peut plausiblement savoir à l'avance combien de compartiments allouer dans la structure de données C sous-jacente et créer la carte inverse sans jamais appeler dictresize, mais cette approche refuse à Python cette possibilité.
Mark Amery
1

Cela gère les valeurs non uniques et conserve une grande partie de l'apparence du boîtier unique.

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

Pour Python 3.x, remplacez itervaluespar values.

Ersatz Kwisatz
la source
3
Cette solution est assez élégante comme un liner et elle gère le cas des valeurs non uniques. Cependant, il a une complexité en O (n2) ce qui signifie qu'il devrait être correct pour plusieurs dizaines d'éléments, mais il serait trop lent pour une utilisation pratique si vous avez plusieurs centaines de milliers d'éléments dans votre dictionnaire initial. Les solutions basées sur un dict par défaut sont bien plus rapides que celle-ci.
gabuzo
Gabuzo a tout à fait raison. Cette version est (sans doute) plus claire que certaines, mais elle n'est pas adaptée aux données volumineuses.
Ersatz Kwisatz
0

La fonction est symétrique pour les valeurs de type list; Les tuples sont convertis en listes lors de l'exécution de reverse_dict (reverse_dict (dictionnaire))

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
la source
0

Étant donné que les dictionnaires nécessitent une clé unique dans le dictionnaire contrairement aux valeurs, nous devons ajouter les valeurs inversées dans une liste de tri à inclure dans les nouvelles clés spécifiques.

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
la source
0

Solution fonctionnelle rapide pour les cartes non bijectives (valeurs non uniques):

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 théorie, cela devrait être plus rapide que d'ajouter un à un à l'ensemble (ou de l'ajouter à la liste) comme dans la solution impérative .

Malheureusement, les valeurs doivent être triables, le tri est requis par groupby.

cjay
la source
1
"En théorie, cela devrait être plus rapide que d'ajouter un à un à l'ensemble (ou de l'ajouter à la liste)" - non. Étant donné les néléments du dict original, votre approche présente une O(n log n)complexité temporelle en raison de la nécessité de trier les éléments du dict, tandis que l'approche impérative naïve a une O(n)complexité temporelle. Pour autant que je sache, votre approche peut être plus rapide jusqu'à des absurdement grands dicten pratique , mais ce n'est certainement pas plus rapide en théorie.
Mark Amery
0

Essayez ceci pour python 2.7 / 3.x

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

Je le ferais de cette façon en python 2.

inv_map = {my_map[x] : x for x in my_map}
genghiscrade
la source
Itérer des paires clé-valeur simultanément via dict.items(ou iteritemsen Python 2) est plus efficace que d'extraire chaque valeur séparément lors de l'itération des clés.
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})

Cela fournira la sortie comme: {1: ['a', 'd'], 2: ['b'], 3: ['c']}

RVR
la source
Itérer des paires clé-valeur simultanément via dict.items(ou iteritemsen Python 2) est plus efficace que d'extraire chaque valeur séparément lors de l'itération des clés. De plus, vous n'avez ajouté aucune explication à une réponse qui en double d'autres.
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

ce code fait ceci:

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
la source
1
Généralement, les réponses sont beaucoup plus utiles si elles incluent une explication de ce que le code est censé faire et pourquoi cela résout le problème sans en introduire d'autres.
Tom Aranda
1
C'est très agréable, mais beaucoup de décisions inexpliquées (par exemple, pourquoi des minuscules pour les clés?)
Liudvikas Akelis
-2

Pas quelque chose de complètement différent, juste une recette un peu réécrite de Cookbook. Il est en outre optimisé en conservant la setdefaultméthode, au lieu de le faire à chaque fois passer par l'instance:

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)

Conçu pour être exécuté sous CPython 3.x, pour 2.x remplacer mapping.items()parmapping.iteritems()

Sur ma machine tourne un peu plus vite que les autres exemples ici

thodnev
la source
1
Construire le résultat en tant que dictpuis convertir à la classe souhaitée à la fin (plutôt que de commencer par une classe du bon type) me semble que cela entraîne un impact sur les performances entièrement évitable, ici.
Mark Amery
-2

J'ai écrit cela à l'aide du cycle 'for' et de la méthode '.get ()' et j'ai changé le nom 'map' du dictionnaire en 'map1' parce que 'map' est une fonction.

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

Si les valeurs ne sont pas uniques ET peuvent être un hachage (une dimension):

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)

Et avec une récursivité si vous avez besoin de creuser plus profondément, alors juste une dimension:

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
la source
Vous pourriez améliorer vos solutions en utilisant un defaultdict: il supprimera toutes les lignes invDict [item] = invDict.get (item, [])
gabuzo
Votre première approche ici se transforme {"foo": "bar"}en {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}et déclenche une exception si une valeur dans myDictn'est pas un itérable. Je ne sais pas quel comportement vous essayez d'implémenter ici, mais ce que vous avez réellement implémenté est quelque chose que personne ne voudra.
Mark Amery