Зворотне / інвертування зіставлення словника


Відповіді:


923

Для Python 2.7.x

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

Для Python 3+:

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

4
В останніх версіях Python 2.7.x також my_map.items()працює
Валентина

29
Це буде працювати, за винятком того, що воно не буде працювати, якщо в значеннях немає єдиності. У такому випадку ви втратите деякі записи
Габузо


2
Так, як деталь реалізації. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Немає гарантій, що він залишиться таким, тому не пишіть код, покладаючись на Dictтаку саму поведінку, як OrderedDict.
Маттіас

9
@Mattias, це справедливо для Python 3.6. Для версії 3.7 збереження замовлення офіційне: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL сказав так.
interDist

174

Якщо припустити, що значення в диктаті унікальні:

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

22
Значення також мають бути доступними
John La Rooy

30
@ Buttons840: Якщо значення не є унікальними, то в будь-якому випадку не існує унікальної інверсії словника або, кажучи іншими словами, інвертування не має сенсу.
Wrzlprmft

2
@ Buttons840 Для значення з’явиться лише остання клавіша. Напевно, немає гарантій на замовлення, яке iteritems()виведе, тому можна припустити, що довільний ключ буде призначений для не унікального значення таким чином, який буде явно відтворюваним за деяких умов, але ні в цілому.
Євгеній Сергєєв

2
Зверніть увагу, звичайно, що в Python 3 вже немає iteritems()методу і такий підхід не буде працювати; використовувати items()замість цього, як показано у прийнятій відповіді. Також розуміння словника зробить це красивішим, ніж дзвінок dict.
Марк Амері

5
@Wrzlprmft Існує природне визначення для зворотного у випадку не унікальних значень. Кожне значення відображається на набір ключів, що ведуть до нього.
Лев

135

Якщо значення my_mapне унікальні:

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

56
... або просто inv_map.setdefault (v, []). додати (k). Раніше я був фанатом засудження за замовчуванням, але потім я занадто багато разів накручувався і робив висновок, що явне насправді краще, ніж неявне.
alsuren

Ця відповідь є некоректною для мульти-карти, додавати тут марно, оскільки значення кожного разу скидається в порожній список, слід використовувати set_default
Ярослав Булатов

1
@YaroslavBulatov ні, код, як показано тут, не порушений - inv_map.get(v, [])повертає вже доданий список, якщо такий є, тому призначення не скидається до порожнього списку. setdefaultвсе-таки було б гарніше.
Марк Амері

10
Набір мав би тут більше сенсу. Клавіші (напевно) є доступними, і порядку немає. inv_map.setdefault(v, set()).add(k).
Artyer

1
У python3 використовуйте my_map.items()замість my_map.iteritems().
apitsch

42

Для цього, зберігаючи тип вашого відображення (якщо припустити, що це dictабо dictпідклас):

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

4
Це може бути розумним, але воно не працює, коли в одному оригінальному словнику більше ніж одна клавіша має однакове значення.
Rafael_Espericueta

1
@Rafael_Espericueta Це справедливо для будь-якої можливої ​​відповіді на це запитання, оскільки карта зі значеннями, які повторюються, не є зворотною.
Марк Амері

2
@Mark_Amery У певному сенсі це може бути зворотним. Наприклад: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D - словник, наприклад {батько: діти}, а Дінв - словник {дитина: батьки}.
Rafael_Espericueta

36

Спробуйте це:

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

(Зауважте, що документи Python у представленнях словників явно гарантують, що вони мають .keys()і .values()їх елементи в тому ж порядку, що дозволяє працювати вищевказаному підходу.)

Як варіант:

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

або за допомогою розуміння дикту python 3.0

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

1
Зауважте, що це працює лише в тому випадку, якщо ключі унікальні (що майже ніколи не буває, якщо ви хочете їх інвертувати).
помер

Згідно з python.org/dev/peps/pep-0274, розуміння дикту також доступні в 2.7+.
Каву

24

Ще один, більш функціональний спосіб:

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

3
Дякуємо за публікацію Я не впевнений, що це є кращим - цитувати Гвідо Ван Россума в PEP 279: " filterі mapповинен померти і потрапити до списків, а не робити більше варіантів".
Брайан М. Хант

2
Так, Брайан. Я просто додавав це як точку розмови. Спосіб розуміння диктату є більш читабельним для більшості, що я собі уявляв. (І, швидше за все, теж я здогадуюсь)
Брендан Магуайр

3
Можливо, буде менш читабельним, ніж інші, але цей спосіб має користь від того, щоб мати можливість обмінятися dictз іншими типами відображення, такими як collections.OrderedDictабоcollections.defaultdict
Will S

10

Це поширюється на відповідь Роберта , застосовуючи до того, коли значення в диктаті не є унікальними.

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

Реалізація обмежена тим, що ви не можете використовувати reversedдвічі та повернути оригінал. Вона не є симетричною як такою. Це тестується з Python 2.6. Ось випадок використання того, як я використовую для надрукування результату дікта.

Якщо ви хотіли б використовувати , setніж list, і може існувати невпорядковані додатки , для яких це має сенс, замість того setdefault(v, []).append(k), використання setdefault(v, set()).add(k).


це також було б хорошим місцем для використання наборів замість списків, тобтоrevdict.setdefault(v, set()).add(k)
мюсло

Звичайно, але це точно, чому це хороший привід використовувати set. Тут застосовується внутрішній тип. Що робити, якщо я хочу знайти всі ключі, де значень немає 1або 2? Тоді я можу просто зробити d.keys() - inv_d[1] - inv_d[2](на Python 3)
мюсло

9

Ми також можемо повернути словник з повторюваними ключами, використовуючи 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']}  

Дивіться тут :

Цей прийом простіший і швидший, ніж еквівалентний метод використання dict.setdefault().


6

Наприклад, у вас є такий словник:

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

І ви хочете отримати його в такому перевернутому вигляді:

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

Перше рішення . Для перетворення пар ключ-значення у словнику використовуйте forпідхід -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)

Друге рішення . Використовуйте підхід для розуміння словника для інверсії:

# Use this code to invert dictionaries that have unique values

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

Третє рішення . Використовуйте зворотний підхід до інверсії (спирається на друге рішення):

# 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зарезервовано і не повинно використовуватися для змінних імен
crypdick

2
забув сказати нам, що my_mapтаке
crypdick

dictio()? Ви мали на увазі dict()?
Георгій

5

Поєднання списку та розуміння словника. Може обробляти повторювані ключі

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

1
Як і stackoverflow.com/a/41861007/1709587 , це O (n²) рішення проблеми, яка легко вирішується в O (n) з парою додаткових рядків коду.
Марк Амері

2

Якщо значення не унікальні, і ви трохи хардкор:

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

Особливо для великого диктату, зауважте, що це рішення набагато менш ефективне, ніж відповідь Python на зворотній / інверсійній карті, тому що вона циклічно items()повторюється.


7
Це просто нечитабельний і хороший приклад того, як не писати підтримуваний код. Не буду, -1тому що вона все ще відповідає на питання, лише моя думка.
Russ Bradberry

1

Крім інших запропонованих вище функцій, якщо вам подобаються лямбда:

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

Або ви могли це зробити і так:

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

2
-1; все, що ви зробили, це взяти інші відповіді зі сторінки та помістити їх у лямбда. Крім того, присвоєння лямбда змінної є порушенням PEP 8 .
Марк Амері

1

Я думаю, що найкращий спосіб зробити це - визначити клас. Ось реалізація "симетричного словника":

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

Методи видалення та ітерації досить просто застосувати, якщо вони потрібні.

Ця реалізація є набагато ефективнішою, ніж перевернення всього словника (який, здається, є найпопулярнішим рішенням на цій сторінці). Не кажучи вже про те, що ви можете додавати або видаляти значення з SymDict скільки завгодно, і ваш зворотний словник завжди залишатиметься дійсним - це неправда, якщо ви просто перевернете весь словник один раз.


Мені подобається ця ідея, хоча було б добре зазначити, що вона торгує додатковою пам’яттю для покращення обчислень. Щасливішим середовищем може бути кешування або ліниво обчислення дзеркала. Варто також зазначити, що це можна зробити більш синтаксично привабливим, наприклад, з переглядами словника та спеціальними операторами.
Брайан М. Хант

@ BrianM.Hunt Він торгує пам'яттю, але не багато. Ви зберігаєте лише два набори покажчиків на кожен об’єкт. Якщо ваші об'єкти набагато більше, ніж одиничні цілі числа, це не призведе до великої різниці. Якщо у вас є величезна таблиця крихітних предметів з іншого боку, вам, можливо, доведеться врахувати ці пропозиції ...
NcAdams,

І я погоджуюся, тут ще багато чого потрібно зробити - я можу це згодом перетворити на повністю функціонуючий тип даних
NcAdams,

2
"Ця реалізація набагато ефективніше, ніж перевернення цілого словника" - гм, чому? Я не бачу правдоподібного способу, коли цей підхід може принести значну користь; у вас ще є два словники таким чином. Якщо що-небудь, я б очікував, що це буде повільніше, ніж, скажімо, інвертування дикту з розумінням, тому що якщо ви інвертуєте дикт, Python може правдоподібно заздалегідь знати, скільки відер виділити в базовій структурі даних C та створити зворотну карту не закликаючи ніколи dictresize, але такий підхід заперечує цю можливість Python.
Марк Амері

1

Це обробляє не унікальні цінності та зберігає значну частину вигляду унікальної справи.

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

Для Python 3.x замініть itervaluesна values.


3
Це рішення є досить елегантним як єдиний вкладиш, і воно керує випадком не унікальних значень. Однак він має складність у O (n2), що означає, що він повинен бути нормальним для декількох десятків елементів, але це було б занадто повільно для практичного використання, якщо у вашому початковому словнику є кілька сотень тисяч елементів. Рішення, засновані на дікті за замовчуванням, набагато швидше, ніж цей.
Габузо

Габузо цілком прав. Ця версія (імовірно) зрозуміліша за деякі, але вона не підходить для великих даних.
Ersatz Kwisatz

0

Функція симетрична для значень списку типів; Кортежі прикриваються списками під час виконання зворотного вироку (reverse_dict (словник))

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

Оскільки для словників потрібен один унікальний ключ у словнику на відміну від значень, ми повинні додати перевернуті значення до списку сортування, які слід включити в нові конкретні ключі.

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

Швидке функціональне рішення для небієктивних карт (значення не унікальні):

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

Теоретично це повинно бути швидшим, ніж додавання до набору (або додавання до списку) по черзі, як у імперативному рішенні .

На жаль, значення мають бути сортованими, сортування потрібно груповим методом.


1
"Теоретично це має бути швидше, ніж додавання до набору (або додавання до списку) по черзі" - ні. Враховуючи nелементи оригінального дикту, ваш підхід має O(n log n)складність у часі через необхідність сортування предметів дикту, тоді як наївно-імперативний підхід має O(n)часову складність. Наскільки я знаю, ваш підхід може бути швидшим до тих пір, поки dictна практиці це не абсурдно велике , але теоретично це не швидше.
Марк Амері


-1

Я би зробив це так у python 2.

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

Ітерація пар ключ-значення одночасно через dict.items(або iteritemsв Python 2) є більш ефективною, ніж вилучення кожного значення окремо під час ітерації ключів.
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})

Це забезпечить вихід: {1: ['a', 'd'], 2: ['b'], 3: ['c']}


Ітерація пар ключ-значення одночасно через dict.items(або iteritemsв Python 2) є більш ефективною, ніж вилучення кожного значення окремо під час ітерації ключів. Крім того, ви не додали жодних пояснень у відповідь, яка дублює інші.
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

цей код роблять так:

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
Як правило, відповіді набагато корисніші, якщо вони включають пояснення того, що призначений для виконання коду та чому це вирішує проблему, не вводячи інших.
Том Аранда

1
Це дуже приємно, але багато незрозумілих рішень (наприклад, чому малі
регістри

-2

Не щось зовсім інше, лише трохи переписаний рецепт з Cookbook. Це ще краще оптимізоване методом збереження setdefault, замість того, щоб щоразу отримувати його через екземпляр:

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)

Розроблений для запуску під CPython 3.x, для 2.x замінить mapping.items()наmapping.iteritems()

На моїй машині працює трохи швидше, ніж інші приклади тут


1
Створення результату як, dictа потім перехід до потрібного класу в кінці (а не починати з класу правильного типу) мені здається, що він спричиняє цілком уникнене враження від продуктивності.
Марк Амері

-2

Я написав це за допомогою циклу 'for' і method '.get ()' і змінив назву 'map' словника на 'map1', оскільки 'map' є функцією.

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

-2

Якщо значення не є унікальними AND, може бути хеш (один вимір):

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)

І з рекурсією, якщо вам потрібно копати глибше, то лише один вимір:

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)

Ви можете покращити свої рішення, використовуючи вирок за замовчуванням: він видалить усі рядки invDict [item] = invDict.get (item, [])
gabuzo

Ваш перший підхід тут перетворюється {"foo": "bar"}на {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}виняток і створює виняток, якщо будь-яке значення в ньому myDictне є ітерабельним. Я не впевнений, яку поведінку ви намагалися тут реалізувати, але те, що ви насправді реалізували, - це щось дуже багато, чого ніхто не збирається хотіти.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.