Обратное / обратное отображение словаря

663

Приведенный словарь выглядит так:

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

Как можно инвертировать эту карту, чтобы получить:

inv_map = {1: 'a', 2: 'b'}
Брайан М. Хант
источник

Ответы:

924

Для 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()}
SilentGhost
источник
4
В последних версиях Python 2.7.x также my_map.items()работает
Валентин
30
Это будет работать за исключением того, что оно не будет работать, если в значениях нет уникальности. В этом случае вы потеряете некоторые записи
gabuzo
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-De December/151283.html . BDFL так и сказал.
InterDist
174

Предполагая, что значения в dict являются уникальными:

dict((v, k) for k, v in my_map.iteritems())
Рик поддерживает Монику
источник
22
Значения тоже должны быть хешируемыми
Джон Ла Рой
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, []). append (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 - это словарь, например, {parent: children}, а Dinv - это словарь {child: parent}.
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)

или используя pyt 3.0

inv_map = {my_map[k] : k for k in my_map}
Сикора
источник
1
Обратите внимание, что это работает только в том случае, если ключи уникальны (что почти никогда не происходит, если вы хотите инвертировать их).
gented
В соответствии с 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

Это расширяет ответ Роберта , применяемый к случаям, когда значения в dict не являются уникальными.

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. Вот пример использования того, как я использую, чтобы напечатать результирующий dict.

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

Акаменус
источник
это также было бы хорошим местом для использования наборов вместо списков, то естьrevdict.setdefault(v, set()).add(k)
mueslo
Конечно, но именно поэтому это хорошая причина для использования set. Это внутренний тип, который применяется здесь. Что делать, если я хочу найти все ключи, значения которых отсутствуют 1или 2? Тогда я могу просто сделать d.keys() - inv_d[1] - inv_d[2](в Python 3)
mueslo
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().

irudyak
источник
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()}
SVJ
источник
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()несколько раз.

PCV
источник
7
Это просто нечитаемый и хороший пример того, как не писать поддерживаемый код. Я не буду, -1потому что это все еще отвечает на вопрос, только мое мнение.
Расс Брэдберри
1

В дополнение к другим функциям, предложенным выше, если вам нравятся лямбды:

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

Или вы можете сделать это тоже так:

invert = lambda mydict: dict( zip(mydict.values(), mydict.keys()) )
RussellStewart
источник
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 столько раз, сколько захотите, и ваш обратный словарь всегда останется действительным - это не так, если вы просто полностью измените весь словарь один раз.

NcAdams
источник
Мне нравится эта идея, хотя было бы хорошо отметить, что она обменивает дополнительную память на улучшенные вычисления. Более счастливой средой может быть кэширование или ленивое вычисление зеркала. Стоит также отметить, что его можно сделать более синтаксически привлекательным, например, с помощью словарных представлений и пользовательских операторов.
Брайан М. Хант
@ BrianM.Hunt Отменяет память, но не так много. Вы храните только два набора указателей на каждый объект. Если ваши объекты намного больше, чем одно целое число, это не будет иметь большого значения. Если у вас есть огромная таблица крошечных предметов с другой стороны, вам, возможно, придется рассмотреть эти предложения ...
NcAdams
И я согласен, что здесь еще многое предстоит сделать - я мог бы конкретизировать это в полностью функционирующий тип данных позже
NcAdams
2
«Эта реализация гораздо эффективнее, чем инвертирование всего словаря» - почему? Я не вижу ни одного правдоподобного способа, которым этот подход мог бы принести значительный выигрыш в производительности; у вас все еще есть два словаря таким образом. Во всяком случае, я ожидал бы, что это будет медленнее, чем, скажем, инвертирование dict с пониманием, потому что, если вы инвертируете dict, 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), что означает, что он должен быть в порядке для нескольких десятков элементов, но это будет слишком медленно для практического использования, если у вас есть несколько сотен тысяч элементов в вашем первоначальном словаре. Решения, основанные на требовании по умолчанию, намного быстрее, чем этот.
Габузо
Габузо совершенно прав. Эта версия (возможно) более понятна, чем некоторые, но она не подходит для больших данных.
Эрзац Квисатц
0

Функция симметрична для значений списка типов; Кортежи включаются в списки при выполнении reverse_dict (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
Alf
источник
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
EyoelD
источник
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
        )
    }

Теоретически это должно быть быстрее, чем добавление к набору (или добавление к списку) один за другим, как в императивном решении .

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

Cjay
источник
1
«Теоретически это должно быть быстрее, чем добавление в набор (или добавление в список) по одному» - нет. Учитывая nэлементы в исходном дикте, ваш подход имеет O(n log n)временную сложность из-за необходимости сортировки элементов диктанта, тогда как наивный императивный подход имеет O(n)временную сложность. Насколько я знаю, ваш подход может быть быстрее вплоть до абсурдно больших dictна практике , но в теории он, конечно, не быстрее.
Марк Амери
0

Попробуйте это для Python 2.7 / 3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map
dhvlnyk
источник
-1

Я бы сделал это таким образом в Python 2.

inv_map = {my_map[x] : x for x in my_map}
genghiscrade
источник
Итерация пар ключ-значение одновременно через 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']}

RVR
источник
Итерация пар ключ-значение одновременно через dict.items(или iteritemsв Python 2) более эффективна, чем извлечение каждого значения отдельно при итерации ключей. Кроме того, вы не добавили объяснения в ответ, который дублирует других.
19
-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']}
Shb8086
источник
1
Как правило, ответы гораздо полезнее, если они включают в себя объяснение того, для чего предназначен код, и почему это решает проблему, не представляя других.
Том Аранда
1
Это очень приятно, но много необъяснимых решений (например, почему строчные для ключей?)
Людвикас Акелис
-2

Не что-то совершенно другое, просто немного переписанный рецепт из поваренной книги. Более того, он оптимизируется путем сохранения 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()

На моей машине работает чуть быстрее, чем на других примерах здесь

thodnev
источник
1
Построение результата как a dictи последующее преобразование в требуемый класс в конце (вместо того, чтобы начинать с класса правильного типа) выглядит для меня так, как будто это приводит к совершенно предотвращаемому падению производительности.
Марк Амери
-2

Я написал это с помощью цикла «for» и метода «.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

Если значения не уникальны, И может быть хешем (одно измерение):

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)
mveith
источник
Вы можете улучшить свои решения, используя defaultdict: он удалит все строки invDict [item] = invDict.get (item, [])
gabuzo
Ваш первый подход здесь превращается {"foo": "bar"}в {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}и вызывает исключение , если какое - либо значение в myDictне итератор. Я не уверен, какое поведение вы пытались реализовать здесь, но то, что вы на самом деле реализовали, это то, чего никто не хочет.
Марк Амери