Inverter / inverter um mapeamento de dicionário

663

Dado um dicionário como este:

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

Como se pode inverter este mapa para obter:

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

Respostas:

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
fonte
4
Nas versões recentes do Python 2.7.x my_map.items(), também funciona
valentin
30
Isso funcionará, exceto que não funcionará se não houver unicidade nos valores. Nesse caso, você perderá algumas entradas
gabuzo 23/10
2
Sim, como um detalhe de implementação. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Não há garantia de que continue assim, portanto, não escreva código contando Dictcom o mesmo comportamento de OrderedDict.
Mattias
9
@ Mattias, isso é verdade para o Python 3.6. Para a versão 3.7, a preservação de pedidos é oficial: mail.python.org/pipermail/python-dev/2017-December/151283.html . A BDFL disse isso.
interDist
174

Supondo que os valores no dict sejam únicos:

dict((v, k) for k, v in my_map.iteritems())
Rick apoia Monica
fonte
22
Os valores têm de ser Hashable também
John La Rooy
30
@ Buttons840: Se os valores não forem únicos, não haverá inversão exclusiva do dicionário ou, com outras palavras, a inversão não faz sentido.
Wrzlprmft
2
@ Buttons840 Apenas a última tecla será exibida para o valor. Provavelmente, não há garantias sobre a ordem que iteritems()será gerada, portanto, pode-se supor que uma chave arbitrária seja atribuída a um valor não exclusivo, de uma maneira que aparentemente seja reproduzível em algumas condições, mas não em geral.
Evgeni Sergeev
2
Observe, é claro, que no Python 3 não existe mais um iteritems()método e essa abordagem não funcionará; use items()-o como mostrado na resposta aceita. Além disso, uma compreensão do dicionário tornaria isso mais bonito do que chamar dict.
Mark Amery
5
@Wrzlprmft Existe uma definição natural para inverso no caso de valores não exclusivos. Todo valor é mapeado para o conjunto de chaves que leva a ele.
Leo
135

Se os valores em my_mapnão forem exclusivos:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)
Robert Rossney
fonte
56
... ou apenas inv_map.setdefault (v, []). append (k). Eu costumava ser um fanboy do padrão, mas depois me ferrei muitas vezes e concluí que realmente explícito é melhor do que implícito.
alsuren
Esta resposta é incorreta para multi-mapa, acrescentar aqui é inútil porque o valor é reposto a lista vazia de cada vez, deve usar set_default
Yaroslav Bulatov
1
@YaroslavBulatov não, o código mostrado aqui não está quebrado - inv_map.get(v, [])retorna a lista já adicionada, se houver, para que a atribuição não seja redefinida para uma lista vazia. setdefaultainda seria mais bonito, no entanto.
Mark-Amery
10
Um conjunto faria mais sentido aqui. As chaves são (provavelmente) laváveis ​​e não há ordem. inv_map.setdefault(v, set()).add(k).
Artyer
1
No python3, use em my_map.items()vez de my_map.iteritems().
Apitsch
42

Para fazer isso, preservando o tipo de seu mapeamento (supondo que seja uma dictou uma dictsubclasse):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))
fs.
fonte
4
Por mais inteligente que seja, mas não funciona quando mais de uma chave tem o mesmo valor no dicionário original.
Rafael_Espericueta
1
@Rafael_Espericueta Isso é verdade para qualquer resposta possível a essa pergunta, já que um mapa com valores repetidos não é invertível.
Mark Amery
2
@ Mark_Amery Pode ser invertível de uma maneira geral. Por exemplo: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]} D é um dicionário de, por exemplo, {parent: children}, enquanto Dinv é o dicionário {child: parents}.
Rafael_Espericueta
36

Tente o seguinte:

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

(Observe que os documentos do Python nas visualizações de dicionário garantem explicitamente isso .keys()e .values()têm seus elementos na mesma ordem, o que permite que a abordagem acima funcione.)

Alternativamente:

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

ou usando as compreensões de ditado do python 3.0

inv_map = {my_map[k] : k for k in my_map}
sykora
fonte
1
Observe que isso só funciona se as chaves forem únicas (o que quase nunca acontece se você quiser invertê-las).
gented
De acordo com python.org/dev/peps/pep-0274, as compreensões de dict também estão disponíveis no 2.7+.
Kawu
24

Outra maneira mais funcional:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Brendan Maguire
fonte
3
Obrigado por publicar. Não tenho certeza se isso é preferível - para citar Guido Van Rossum no PEP 279: " filtere mapdeve morrer e ser incluído na compreensão da lista, e não aumentar mais variantes".
Brian M. Hunt
2
Sim, esse é um argumento justo, Brian. Eu estava apenas adicionando isso como um ponto de conversa. O modo de compreensão do ditado é mais legível para a maioria dos que eu imagino. (E provavelmente mais rápido também eu acho)
Brendan Maguire
3
Pode ser menos legível do que outros, mas desta forma tem a vantagem de ser capaz de trocar dictcom outros tipos de mapeamento, como collections.OrderedDictoucollections.defaultdict
Will S
10

Isso se expande com a resposta de Robert , aplicada quando os valores no ditado não são ú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

A implementação é limitada, pois você não pode usar reversedduas vezes e obter o original de volta. Não é simétrico como tal. É testado com o Python 2.6. Aqui está um caso de uso de como estou usando para imprimir o ditado resultante.

Se você preferir usar a do setque a list, e pode haver aplicativos não ordenados para os quais isso faz sentido, em vez de setdefault(v, []).append(k)usar setdefault(v, set()).add(k).

Acumenus
fonte
Isso também seria um bom lugar para usar conjuntos em vez de listas, ou sejarevdict.setdefault(v, set()).add(k)
mueslo
Claro, mas é exatamente por isso que é uma boa razão para usar set. É o tipo intrínseco que se aplica aqui. E se eu quiser encontrar todas as chaves onde os valores não estão 1ou 2? Então eu posso apenas fazer d.keys() - inv_d[1] - inv_d[2](em Python 3) #
mueslo
9

Também podemos reverter um dicionário com chaves 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']}  

Veja aqui :

Essa técnica é mais simples e rápida do que uma técnica equivalente dict.setdefault().

irudyak
fonte
6

Por exemplo, você tem o seguinte dicionário:

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

E você quer obtê-lo de forma invertida:

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

Primeira solução . Para inverter pares de valores-chave em seu dicionário, use uma forabordagem -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)

Segunda solução . Use uma abordagem de compreensão de dicionário para inversão:

# Use this code to invert dictionaries that have unique values

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

Terceira solução . Use a reversão da abordagem de inversão (depende da segunda solução):

# 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
fonte
4
dicté reservado e não deve ser usado para nomes de variáveis
crypdick
2
esqueceu-se de nos dizer o que my_mapé
crypdick
dictio()? Você quis dizer dict()?
Georgy
5

Combinação de lista e compreensão de dicionário. Pode lidar com chaves duplicadas

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}
SVJ
fonte
1
Como stackoverflow.com/a/41861007/1709587 , esta é uma solução de O (n²) para um problema que é facilmente resolvido em O (n) com algumas linhas de código extras.
Mark Amery
2

Se os valores não forem únicos e você for um pouco incondicional:

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 um ditado grande, observe que essa solução é muito menos eficiente que a resposta Python inverte / inverte um mapeamento, porque ele faz um loop items()várias vezes.

pcv
fonte
7
Isso é ilegível e um bom exemplo de como não escrever código de manutenção. Não vou, -1porque ainda responde à pergunta, apenas a minha opinião.
Russ Bradberry
1

Além das outras funções sugeridas acima, se você gosta de lambdas:

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

Ou você também pode fazer o seguinte:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
fonte
2
-1; tudo o que você fez foi pegar outras respostas da página e colocá-las em uma lambda. Além disso, atribuir um lambda a uma variável é uma violação do PEP 8 .
Mark Amery
1

Eu acho que a melhor maneira de fazer isso é definir uma classe. Aqui está uma implementação de um "dicionário 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

Os métodos de exclusão e iteração são fáceis de implementar, se necessários.

Essa implementação é muito mais eficiente do que inverter um dicionário inteiro (que parece ser a solução mais popular nesta página). Sem mencionar, você pode adicionar ou remover valores do seu SymDict o quanto quiser, e seu dicionário inverso sempre permanecerá válido - isso não é verdade se você simplesmente reverter o dicionário inteiro uma vez.

NcAdams
fonte
Gosto dessa idéia, embora seja bom observar que ela troca memória adicional para obter uma computação aprimorada. Um meio mais feliz pode estar armazenando em cache ou computando preguiçosamente o espelho. Também vale a pena notar que isso poderia se tornar mais atraente sintaticamente com, por exemplo, visualizações de dicionário e operadores personalizados.
Brian M. caça
@ BrianM.Hunt Troca de memória, mas não muito. Você armazena apenas dois conjuntos de ponteiros para cada objeto. Se seus objetos forem muito maiores que números inteiros únicos, isso não fará muita diferença. Se você tem uma mesa enorme de objetos minúsculos, por outro lado, pode ser que você deva considerar essas sugestões ...
NcAdams
E eu concordo, não há mais a ser feito aqui - eu poderia concretizar isso em um tipo de dados totalmente funcional mais tarde
NcAdams
2
"Esta implementação é muito mais eficiente do que inverter um dicionário inteiro" - hum, por quê? Não vejo nenhuma maneira plausível dessa abordagem ter um benefício significativo de desempenho; você ainda tem dois dicionários dessa maneira. De qualquer forma, eu esperaria que isso fosse mais lento do que, digamos, inverter o ditado com uma compreensão, porque se você inverter o ditado, o Python pode plausivelmente saber antecipadamente quantos buckets alocar na estrutura de dados C subjacente e criar o mapa inverso sem nunca ligar dictresize, mas essa abordagem nega ao Python essa possibilidade.
21816 Mark Amery
1

Isso lida com valores não exclusivos e mantém grande parte da aparência do caso exclusivo.

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

Para Python 3.x, substitua itervaluespor values.

Ersatz Kwisatz
fonte
3
Essa solução é bastante elegante como uma linha e gerencia o caso de valores não exclusivos. No entanto, ele possui uma complexidade em O (n2), o que significa que deve ser aceitável para várias dezenas de elementos, mas seria muito lento para uso prático se você tiver várias centenas de milhares de elementos em seu dicionário inicial. As soluções baseadas em um ditado padrão são muito mais rápidas que este.
gabuzo
Gabuzo está certo. Esta versão é (sem dúvida) mais clara que algumas, mas não é adequada para grandes dados.
Ersatz Kwisatz
0

Função é simétrica para valores da lista de tipos; As tuplas são ocultas em listas ao executar reverse_dict (reverse_dict (dictionary))

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
fonte
0

Como os dicionários exigem uma chave exclusiva no dicionário, diferentemente dos valores, precisamos anexar os valores revertidos em uma lista de classificação a ser incluída nas novas chaves 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
fonte
0

Solução funcional rápida para mapas não-bijetivos (valores não exclusivos):

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

Em teoria, isso deve ser mais rápido do que adicionar ao conjunto (ou anexar à lista) um por um, como na solução imperativa .

Infelizmente, os valores precisam ser classificados, a classificação é exigida por groupby.

cjay
fonte
1
"Em teoria, isso deve ser mais rápido do que adicionar ao conjunto (ou anexar à lista) um por um" - não. Dados os nelementos no ditado original, sua abordagem tem O(n log n)complexidade de tempo devido à necessidade de classificar os itens do ditado, enquanto a abordagem imperativa ingênua tem O(n)complexidade de tempo. Pelo que sei, sua abordagem pode ser mais rápida, até absurdamente grandedict na prática , mas certamente não é mais rápida na teoria.
21816 Mark Amery
0

Tente isso para python 2.7 / 3.x

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

Eu faria assim em python 2.

inv_map = {my_map[x] : x for x in my_map}
genghiscrade
fonte
Iterar pares de valores-chave simultaneamente via dict.items(ou iteritemsno Python 2) é mais eficiente do que extrair cada valor separadamente enquanto itera as chaves.
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})

Isso fornecerá a saída como: {1: ['a', 'd'], 2: ['b'], 3: ['c']}

RVR
fonte
Iterar pares de valores-chave simultaneamente via dict.items(ou iteritemsno Python 2) é mais eficiente do que extrair cada valor separadamente enquanto itera as chaves. Além disso, você não adicionou nenhuma explicação a uma resposta que duplica outras.
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 faz assim:

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
fonte
1
Geralmente, as respostas são muito mais úteis se incluem uma explicação sobre o que o código pretende fazer e por que isso resolve o problema sem a introdução de outros.
Tom Aranda
1
É muito bom, mas um monte de decisões inexplicáveis (por exemplo, caso, por mais baixo para chaves?)
Liudvikas Akelis
-2

Não é algo completamente diferente, apenas uma receita reescrita do Cookbook. Além disso, é otimizado pelo setdefaultmétodo de retenção , em vez de obtê-lo através da instância:

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)

Projetado para ser executado no CPython 3.x, para 2.x substitua mapping.items()pormapping.iteritems()

Na minha máquina roda um pouco mais rápido do que outros exemplos aqui

thodnev
fonte
1
Construir o resultado como um dicte depois converter para a classe desejada no final (em vez de começar com uma classe do tipo certo) me parece que incorre em um impacto de desempenho totalmente evitável aqui.
Mark Amery
-2

Escrevi isso com a ajuda do ciclo 'for' e do método '.get ()' e mudei o nome 'map' do dicionário para 'map1' porque 'map' é uma função.

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

Se os valores não forem únicos E puder ser um hash (uma dimensão):

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 com uma recursão, se você precisar ir mais fundo, apenas uma dimensão:

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
fonte
Você pode melhorar suas soluções usando um defaultdict: ele vai remover todas as invDict [artigo] = invDict.get (item, []) linhas
gabuzo
Sua primeira abordagem aqui converte {"foo": "bar"}para {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}e levanta uma exceção se qualquer valor myDictnão é um iterável. Não tenho certeza de qual comportamento você estava tentando implementar aqui, mas o que você realmente implementou é algo que praticamente ninguém vai querer.
Mark Amery