Membalikkan / membalikkan pemetaan kamus

663

Diberikan kamus seperti ini:

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

Bagaimana seseorang membalik peta ini untuk mendapatkan:

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

Jawaban:

924

Untuk Python 2.7.x

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

Untuk Python 3+:

inv_map = {v: k for k, v in my_map.items()}
SilentGhost
sumber
4
Dalam versi Python 2.7.x baru-baru ini juga my_map.items()berfungsi
valentin
30
Ini akan berfungsi kecuali bahwa itu tidak akan berfungsi jika tidak ada unicity dalam nilai. Dalam hal ini Anda akan kehilangan beberapa entri
gabuzo
2
Ya, sebagai detail implementasi. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Tidak ada jaminan itu akan tetap seperti itu jadi jangan menulis kode dengan mengandalkan Dictperilaku yang sama OrderedDict.
Mattias
9
@ Matias, ini berlaku untuk Python 3.6. Untuk versi 3.7, pelestarian pesanan adalah resmi: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL mengatakan demikian.
interDist
174

Dengan asumsi bahwa nilai dalam dikt adalah unik:

dict((v, k) for k, v in my_map.iteritems())
Rick mendukung Monica
sumber
22
Nilai-nilai juga harus hashable
John La Rooy
30
@ Buttons840: Jika nilai tidak unik, tidak ada inversi unik dari kamus atau, dengan kata lain, pembalikan tidak masuk akal.
Wrzlprmft
2
@ Buttons840 Hanya kunci terakhir yang akan muncul untuk nilainya. Mungkin tidak ada jaminan pada urutan yang iteritems()akan dihasilkan, sehingga dapat diasumsikan bahwa kunci arbitrer akan diberikan untuk nilai yang tidak unik, dengan cara yang tampaknya akan dapat diproduksi kembali dalam beberapa kondisi, tetapi tidak demikian pada umumnya.
Evgeni Sergeev
2
Perhatikan, tentu saja, bahwa dalam Python 3 tidak ada lagi iteritems()metode dan pendekatan ini tidak akan berfungsi; gunakan di items()sana sebagai gantinya ditunjukkan pada jawaban yang diterima. Juga, pemahaman kamus akan membuat ini lebih cantik daripada menelepon dict.
Mark Amery
5
@Wrzlprmft Ada definisi alami untuk kebalikan dalam kasus nilai-nilai yang tidak unik. Setiap nilai dipetakan ke set kunci yang mengarah ke sana.
Leo
135

Jika nilai dalam my_maptidak unik:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, [])
    inv_map[v].append(k)
Robert Rossney
sumber
56
... atau hanya inv_map.setdefault (v, []). append (k). Saya dulunya adalah seorang fanboydictdict, tapi kemudian saya menjadi kacau sekali dan menyimpulkan bahwa sebenarnya eksplisit lebih baik daripada implisit.
alsuren
Jawaban ini tidak benar untuk multi-peta, tambahkan di sini tidak berguna karena nilai disetel ulang ke daftar kosong setiap kali, harus menggunakan set_default
Yaroslav Bulatov
1
@YaroslavBulatov tidak, kode seperti yang ditunjukkan di sini tidak rusak - inv_map.get(v, [])mengembalikan daftar yang sudah ditambahkan jika ada, sehingga tugas tidak diatur ulang ke daftar kosong. setdefaultmasih akan lebih cantik.
Mark Amery
10
Satu set akan lebih masuk akal di sini. Kuncinya adalah (mungkin) hashable, dan tidak ada pesanan. inv_map.setdefault(v, set()).add(k).
Artyer
1
Di python3, gunakan my_map.items()sebagai ganti my_map.iteritems().
apitsch
42

Untuk melakukan ini sambil mempertahankan jenis pemetaan Anda (dengan asumsi bahwa itu adalah dictatau dictsubkelas):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))
fs.
sumber
4
Pintar mungkin, tetapi tidak berfungsi ketika lebih dari satu kunci memiliki nilai yang sama dalam kamus asli.
Rafael_Espericueta
1
@Rafael_Espericueta Itu benar dari setiap jawaban yang mungkin untuk pertanyaan ini, karena peta dengan nilai yang diulang tidak dapat dibalik.
Mark Amery
2
@ Mark_Amery Ini bisa dibalik secara umum, dalam arti tertentu. Misalnya: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D adalah kamus misalnya {parent: children}, sedangkan Dinv adalah kamus {child: parents}.
Rafael_Espericueta
36

Coba ini:

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

(Perhatikan bahwa dokumentasi Python pada tampilan kamus secara eksplisit menjamin hal itu .keys()dan .values()elemen-elemennya dalam urutan yang sama, yang memungkinkan pendekatan di atas berfungsi.)

Kalau tidak:

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

atau menggunakan pemahaman dict python 3.0

inv_map = {my_map[k] : k for k in my_map}
sykora
sumber
1
Perhatikan bahwa ini hanya berfungsi jika kunci unik (yang hampir tidak pernah terjadi jika Anda ingin membalikkannya).
gented
Menurut python.org/dev/peps/pep-0274, pemahaman dikt juga tersedia dalam 2.7+.
Kawu
24

Cara lain yang lebih fungsional:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Brendan Maguire
sumber
3
Terima kasih sudah memposting. Saya tidak yakin ini lebih disukai - mengutip Guido Van Rossum dalam PEP 279: " filterdan mapharus mati dan dimasukkan ke dalam daftar pemahaman, bukan menumbuhkan lebih banyak varian".
Brian M. Hunt
2
Ya, itu poin yang bagus Brian. Saya hanya menambahkannya sebagai titik percakapan. Cara pemahaman dict lebih mudah dibaca untuk sebagian besar yang saya bayangkan. (Dan sepertinya lebih cepat juga saya kira)
Brendan Maguire
3
Mungkin lebih mudah dibaca daripada yang lain, tetapi cara ini memang memiliki manfaat untuk dapat bertukar dictdengan tipe pemetaan lain seperti collections.OrderedDictataucollections.defaultdict
Will S
10

Ini memperluas jawaban oleh Robert , berlaku ketika nilai-nilai dalam dikt tidak unik.

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

Implementasinya terbatas karena Anda tidak dapat menggunakan reverseddua kali dan mendapatkan yang asli kembali. Itu tidak simetris seperti itu. Ini diuji dengan Python 2.6. Berikut ini adalah contoh penggunaan tentang bagaimana saya menggunakan untuk mencetak dict yang dihasilkan.

Jika Anda lebih suka menggunakan a setdaripada a list, dan mungkin ada aplikasi yang tidak diurutkan yang artinya ini masuk akal, alih-alih setdefault(v, []).append(k)digunakan setdefault(v, set()).add(k).

Acumenus
sumber
ini juga akan menjadi tempat yang baik untuk menggunakan set alih-alih daftar, yaiturevdict.setdefault(v, set()).add(k)
mueslo
Tentu saja, tapi itulah alasan mengapa itu alasan yang baik untuk digunakan set. Ini adalah tipe intrinsik yang berlaku di sini. Bagaimana jika saya ingin menemukan semua kunci di mana nilainya tidak 1atau 2? Maka saya bisa melakukan d.keys() - inv_d[1] - inv_d[2](dalam Python 3)
mueslo
9

Kami juga dapat membalikkan kamus dengan kunci duplikat menggunakan 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']}  

Lihat di sini :

Teknik ini lebih sederhana dan lebih cepat daripada menggunakan teknik setara dict.setdefault().

irudyak
sumber
6

Misalnya, Anda memiliki kamus berikut:

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

Dan Anda ingin mendapatkannya dalam bentuk terbalik:

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

Solusi Pertama . Untuk membalikkan pasangan nilai kunci dalam kamus Anda, gunakan forpendekatan -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)

Solusi Kedua . Gunakan pendekatan pemahaman kamus untuk inversi:

# Use this code to invert dictionaries that have unique values

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

Solusi Ketiga . Gunakan membalikkan pendekatan inversi (bergantung pada solusi kedua):

# 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
sumber
4
dictdicadangkan dan tidak boleh digunakan untuk nama variabel
crypdick
2
lupa untuk memberitahu kami apa my_mapyang
crypdick
dictio()? Apakah maksud Anda dict()?
Georgy
5

Kombinasi pemahaman daftar dan kamus. Dapat menangani kunci duplikat

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}
SVJ
sumber
1
Seperti stackoverflow.com/a/41861007/1709587 , ini adalah solusi O (n²) untuk masalah yang mudah diselesaikan di O (n) dengan beberapa baris kode tambahan.
Mark Amery
2

Jika nilainya tidak unik, dan Anda sedikit 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())
)

Khusus untuk dikt besar, perhatikan bahwa solusi ini jauh lebih efisien daripada jawaban Python membalikkan / membalikkan pemetaan karena berulang items()kali berulang.

pcv
sumber
7
Ini benar-benar tidak dapat dibaca dan merupakan contoh yang baik tentang bagaimana tidak menulis kode yang bisa dikelola. Saya tidak akan melakukannya -1karena masih menjawab pertanyaan, hanya pendapat saya.
Russ Bradberry
1

Selain fungsi-fungsi lain yang disarankan di atas, jika Anda suka lambdas:

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

Atau, Anda bisa melakukannya dengan cara ini juga:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
sumber
2
-1; semua yang telah Anda lakukan diambil jawaban lain dari halaman dan memasukkannya ke dalam lambda. Juga, menugaskan lambda ke variabel adalah pelanggaran PEP 8 .
Mark Amery
1

Saya pikir cara terbaik untuk melakukan ini adalah dengan mendefinisikan kelas. Berikut ini adalah implementasi dari "kamus simetris":

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

Metode penghapusan dan iterasi cukup mudah untuk diterapkan jika diperlukan.

Implementasi ini jauh lebih efisien daripada membalik seluruh kamus (yang tampaknya menjadi solusi paling populer di halaman ini). Belum lagi, Anda dapat menambah atau menghapus nilai dari SymDict Anda sebanyak yang Anda inginkan, dan kamus terbalik Anda akan selalu tetap valid - ini tidak benar jika Anda hanya membalikkan seluruh kamus sekali saja.

NcAdams
sumber
Saya suka ide ini, meskipun akan lebih baik untuk dicatat bahwa itu menukar memori tambahan untuk mencapai komputasi yang ditingkatkan. Media yang lebih bahagia mungkin caching atau malas menghitung cermin. Perlu juga dicatat bahwa itu bisa dibuat lebih menarik secara sintaksis dengan misalnya tampilan kamus dan operator khusus.
Brian M. Hunt
@ BrianM.Hunt Berdagang dari ingatan, tapi tidak banyak. Anda hanya menyimpan dua set pointer ke setiap objek. Jika objek Anda jauh lebih besar dari bilangan bulat tunggal, ini tidak akan membuat banyak perbedaan. Jika Anda memiliki meja besar benda-benda kecil di sisi lain, Anda mungkin perlu mempertimbangkan saran-saran itu ...
NcAdams
Dan saya setuju, masih banyak yang harus dilakukan di sini - saya mungkin menyempurnakan ini menjadi tipe data yang berfungsi penuh nanti
NcAdams
2
"Implementasi ini jauh lebih efisien daripada membalik seluruh kamus" - um, mengapa? Saya tidak melihat cara yang masuk akal bahwa pendekatan ini dapat memiliki manfaat kinerja yang signifikan; Anda masih memiliki dua kamus dengan cara ini. Jika ada, saya berharap ini lebih lambat daripada, katakanlah, membalikkan dikt dengan pemahaman, karena jika Anda membalikkan dikt, Python masuk akal dapat mengetahui sebelumnya berapa banyak ember yang akan dialokasikan dalam struktur data C yang mendasarinya dan membuat peta terbalik tanpa pernah menelepondictresize , tetapi pendekatan ini menolak Python kemungkinan itu.
Mark Amery
1

Ini menangani nilai-nilai non-unik dan mempertahankan banyak tampilan kasing unik.

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

Untuk Python 3.x, ganti itervaluesdengan values.

Ersatz Kwisatz
sumber
3
Solusi ini cukup elegan sebagai liner satu dan mengelola kasus nilai tidak unik. Namun ia memiliki kompleksitas dalam O (n2) yang artinya harus ok untuk beberapa lusinan elemen tetapi akan terlalu lambat untuk penggunaan praktis jika Anda memiliki beberapa ratus ribu elemen dalam kamus awal Anda. Solusi yang didasarkan pada dikt default jauh lebih cepat daripada yang ini.
gabuzo
Gabuzo benar. Versi ini (bisa dibilang) lebih jelas daripada beberapa, tetapi tidak cocok untuk data besar.
Ersatz Kwisatz
0

Fungsi simetris untuk nilai daftar tipe; Tuples dilindungi ke daftar saat melakukan reverse_dict (reverse_dict (kamus))

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

Karena kamus memerlukan satu kunci unik dalam kamus tidak seperti nilai, kami harus menambahkan nilai yang dibalik ke dalam daftar pengurutan untuk dimasukkan dalam kunci spesifik baru.

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

Solusi fungsional cepat untuk peta non-bijektif (nilai tidak unik):

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

Secara teori ini harus lebih cepat daripada menambahkan ke set (atau menambahkan ke daftar) satu per satu seperti dalam solusi imperatif .

Sayangnya nilai-nilai harus diurutkan, pengurutan diperlukan oleh groupby.

cjay
sumber
1
"Secara teori ini harus lebih cepat daripada menambahkan ke set (atau menambahkan ke daftar) satu per satu" - tidak. Diberikan nunsur-unsur dalam diktik asli, pendekatan Anda memiliki O(n log n)kompleksitas waktu karena kebutuhan untuk mengurutkan item-item dict, sedangkan pendekatan imperatif naif memiliki O(n)kompleksitas waktu. Sejauh yang saya tahu pendekatan Anda mungkin lebih cepat sampai besar bukan kepalang dictdalam praktek , tetapi tentu saja tidak lebih cepat secara teori.
Mark Amery
0

Coba ini untuk python 2.7 / 3.x

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

Saya akan melakukannya dengan python 2.

inv_map = {my_map[x] : x for x in my_map}
genghiscrade
sumber
Iterasi pasangan nilai kunci secara bersamaan melalui dict.items(atau iteritemsdengan Python 2) lebih efisien daripada mengekstraksi setiap nilai secara terpisah saat iterasi kunci.
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})

Ini akan menghasilkan output sebagai: {1: ['a', 'd'], 2: ['b'], 3: ['c']}

RVR
sumber
Iterasi pasangan nilai kunci secara bersamaan melalui dict.items(atau iteritemsdengan Python 2) lebih efisien daripada mengekstraksi setiap nilai secara terpisah saat iterasi kunci. Juga, Anda tidak menambahkan penjelasan pada jawaban yang menduplikasi orang lain.
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

kode ini suka ini:

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
sumber
1
Secara umum, jawaban akan jauh lebih membantu jika mereka menyertakan penjelasan tentang apa yang dimaksudkan untuk dilakukan oleh kode, dan mengapa hal itu menyelesaikan masalah tanpa memperkenalkan orang lain.
Tom Aranda
1
Ini sangat bagus, tetapi banyak keputusan yang tidak dapat dijelaskan (misalnya, mengapa huruf kecil untuk kunci?)
Liudvikas Akelis
-2

Bukan sesuatu yang sama sekali berbeda, hanya resep yang ditulis ulang sedikit dari Cookbook. Lebih lanjut dioptimalkan dengan mempertahankan setdefaultmetode, daripada setiap kali mendapatkannya melalui contoh:

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)

Dirancang untuk dijalankan di bawah CPython 3.x, untuk 2.x ganti mapping.items()denganmapping.iteritems()

Di komputer saya berjalan sedikit lebih cepat, daripada contoh lain di sini

thodnev
sumber
1
Membangun hasil sebagai a dictdan kemudian mengonversi ke kelas yang diinginkan di akhir (daripada memulai dengan kelas dari jenis yang tepat) tampak bagi saya seperti itu menimbulkan hit kinerja yang sepenuhnya dapat dihindari, di sini.
Mark Amery
-2

Saya menulis ini dengan bantuan siklus 'untuk' dan metode '.get ()' dan saya mengubah nama 'peta' dari kamus menjadi 'map1' karena 'peta' adalah fungsi.

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

Jika nilai tidak unik DAN mungkin berupa hash (satu dimensi):

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)

Dan dengan rekursi jika Anda perlu menggali lebih dalam maka hanya satu dimensi:

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
sumber
Anda dapat meningkatkan solusi Anda menggunakan defaultdict: itu akan menghapus semua invDict [item] = invDict.get (item, []) baris
gabuzo
Pendekatan pertama Anda di sini mengonversi {"foo": "bar"}ke {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}dan menimbulkan pengecualian jika nilai apa pun di myDictdalamnya tidak dapat diubah. Saya tidak yakin perilaku apa yang Anda coba terapkan di sini, tetapi apa yang sebenarnya Anda implementasikan adalah sesuatu yang tidak diinginkan oleh banyak orang.
Mark Amery