Đảo ngược / đảo ngược ánh xạ từ điển


662

Đưa ra một từ điển như vậy:

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

Làm thế nào một người có thể đảo ngược bản đồ này để có được:

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

Câu trả lời:


922

Đối với Python 2.7.x

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

Đối với Python 3+:

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

4
Trong các phiên bản Python 2.7.x gần đây my_map.items()cũng hoạt động tốt
valentin

29
Điều này sẽ hoạt động ngoại trừ việc nó sẽ không hoạt động nếu không có sự thống nhất trong các giá trị. Trong trường hợp đó, bạn sẽ mất một số mục
gabuzo


2
Vâng, như một chi tiết thực hiện. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon. Không có gì đảm bảo nó sẽ giữ nguyên như vậy vì vậy đừng viết mã dựa trên Dictviệc có hành vi tương tự như OrderedDict.
Mattias

9
@Mattias, điều này đúng với Python 3.6. Đối với phiên bản 3.7, việc bảo quản đơn hàng là chính thức: mail.python.org/pipermail/python-dev/2017-December/151283.html . BDFL đã nói như vậy.
interDist

174

Giả sử rằng các giá trị trong dict là duy nhất:

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

22
Các giá trị cũng phải được băm
John La Rooy

30
@ Nút840: Nếu các giá trị không phải là duy nhất, thì không có đảo ngược duy nhất của từ điển hoặc, nói cách khác, đảo ngược không có nghĩa.
Wrzlprmft

2
@ Nút840 Chỉ khóa cuối cùng sẽ xuất hiện cho giá trị. Có lẽ không có sự đảm bảo nào về thứ tự iteritems()sẽ xuất ra, do đó, có thể giả định rằng một khóa tùy ý sẽ được gán cho một giá trị không duy nhất, theo cách có thể tái tạo rõ ràng trong một số điều kiện, nhưng nói chung là không thể.
Evgeni Sergeev

2
Tất nhiên, lưu ý rằng trong Python 3 không còn iteritems()phương thức nữa và phương pháp này sẽ không hoạt động; sử dụngitems() ở đó thay vì như trong câu trả lời được chấp nhận. Ngoài ra, một sự hiểu biết từ điển sẽ làm cho điều này đẹp hơn so với gọi dict.
Mark Amery

5
@Wrzlprmft Có một định nghĩa tự nhiên cho nghịch đảo trong trường hợp giá trị không duy nhất. Mỗi giá trị được ánh xạ tới tập hợp các khóa dẫn đến nó.
Leo

135

Nếu các giá trị my_mapkhông phải là duy nhất:

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

56
... hoặc chỉ inv_map.setdefault (v, []). chắp thêm (k). Tôi đã từng là một fanboy mặc định, nhưng sau đó tôi đã bị lừa một lần quá nhiều lần và kết luận rằng thực sự rõ ràng là tốt hơn so với ngầm.
alsuren

Câu trả lời này không chính xác cho đa bản đồ, thêm vào đây là vô ích vì giá trị được đặt lại thành danh sách trống mỗi lần, nên sử dụng set_default
Yaroslav Bulatov

1
@YaroslavBulatov không, mã như được hiển thị ở đây không bị hỏng - inv_map.get(v, [])trả về danh sách đã thêm nếu có, vì vậy bài tập không được đặt lại vào danh sách trống. setdefaultvẫn sẽ đẹp hơn
Mark Amery

10
Một bộ sẽ có ý nghĩa hơn ở đây. Các khóa có thể được băm và không có thứ tự. inv_map.setdefault(v, set()).add(k).
Artyer

1
Trong python3, sử dụng my_map.items()thay vì my_map.iteritems().
apitsch

42

Để làm điều này trong khi bảo tồn loại ánh xạ của bạn (giả sử rằng đó là một dicthoặc một dictlớp con):

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

4
Có thể thông minh, nhưng nó không hoạt động khi có nhiều hơn một khóa có cùng giá trị trong từ điển gốc.
Rafael_Espericueta

1
@Rafael_Espericueta Điều đó đúng với bất kỳ câu trả lời nào có thể cho câu hỏi này, vì một bản đồ với các giá trị được lặp lại là không thể đảo ngược.
Đánh dấu Amery

2
@Mark_Amery Nó có thể đảo ngược nói chung, theo một nghĩa nào đó. Ví dụ: D = {1: [1, 2], 2: [2, 3], 3: [1]}, Dinv = {1: [1, 3], 2: [1, 2], 3: [2]}. D là một từ điển ví dụ {cha mẹ: con}, trong khi Dinv là từ điển {con: cha mẹ}.
Rafael_Espericueta

36

Thử cái này:

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

(Lưu ý rằng các tài liệu Python trên chế độ xem từ điển đảm bảo rõ ràng .keys().values()có các phần tử của chúng theo cùng một thứ tự, cho phép cách tiếp cận ở trên hoạt động.)

Cách khác:

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

hoặc sử dụng nhận thức chính tả của python 3.0

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

1
Lưu ý rằng điều này chỉ hoạt động nếu các khóa là duy nhất (gần như không bao giờ xảy ra nếu bạn muốn đảo ngược chúng).
gạc

Theo python.org/dev/peps/pep-0274 hiểu chính tả cũng có sẵn trong 2.7+.
Kawu

24

Một cách khác, nhiều chức năng hơn:

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

3
Cảm ơn vì đăng. Tôi không chắc điều này là thích hợp hơn - trích dẫn Guido Van Rossum trong PEP 279: " filtermapnên chết và được đưa vào danh sách hiểu, không phát triển thêm các biến thể".
Brian M. Hunt

2
Vâng, đó là một điểm công bằng Brian. Tôi chỉ thêm nó như một điểm của cuộc trò chuyện. Cách hiểu chính tả dễ đọc hơn đối với hầu hết tôi tưởng tượng. (Và có khả năng cũng nhanh hơn tôi đoán)
Brendan Maguire

3
Có thể ít đọc hơn những người khác, nhưng cách này có lợi ích là có thể trao đổi dictvới các loại ánh xạ khác như collections.OrderedDicthoặccollections.defaultdict
Will S

10

Điều này mở rộng dựa trên câu trả lời của Robert , áp dụng cho khi các giá trị trong chính tả không phải là duy nhất.

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

Việc triển khai bị hạn chế ở chỗ bạn không thể sử dụng reversedhai lần và lấy lại bản gốc. Nó không đối xứng như vậy. Nó được thử nghiệm với Python 2.6. Đây là một trường hợp sử dụng về cách tôi đang sử dụng để in dict kết quả.

Nếu bạn muốn sử dụng sethơn một list, và có thể tồn tại các ứng dụng không có thứ tự mà điều này có ý nghĩa, thay vì setdefault(v, []).append(k)sử dụng setdefault(v, set()).add(k).


đây cũng sẽ là một nơi tốt để sử dụng các bộ thay vì danh sách, tức làrevdict.setdefault(v, set()).add(k)
mueslo

Tất nhiên, nhưng đó chính xác là lý do tại sao đó là một lý do tốt để sử dụng set. Đó là loại nội tại áp dụng ở đây. Điều gì xảy ra nếu tôi muốn tìm tất cả các khóa trong đó các giá trị không 1hoặc 2? Sau đó tôi chỉ có thể làm d.keys() - inv_d[1] - inv_d[2](trong Python 3)
mueslo

9

Chúng tôi cũng có thể đảo ngược một từ điển với các khóa trùng lặp bằng cách sử dụng 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']}  

Xem tại đây :

Kỹ thuật này đơn giản và nhanh hơn kỹ thuật tương đương dict.setdefault().


6

Chẳng hạn, bạn có từ điển sau:

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

Và bạn muốn có được nó trong một hình thức đảo ngược như vậy:

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

Giải pháp đầu tiên . Để đảo ngược các cặp khóa-giá trị trong từ điển của bạn, hãy sử dụng forcách tiếp cận -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)

Giải pháp thứ hai . Sử dụng một cách tiếp cận hiểu từ điển cho đảo ngược:

# Use this code to invert dictionaries that have unique values

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

Giải pháp thứ ba . Sử dụng hoàn nguyên phương pháp đảo ngược (dựa vào giải pháp thứ hai):

# 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được bảo lưu và không nên được sử dụng cho tên biến
crypdick

2
quên nói cho chúng tôi biết đó my_maplà gì
crypdick

dictio()? Ý bạn là dict()sao
Georgy

5

Kết hợp danh sách và hiểu từ điển. Có thể xử lý các khóa trùng lặp

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

1
Giống như stackoverflow.com/a/41861007/1709587 , đây là một giải pháp O (n²) cho một vấn đề dễ dàng được giải quyết trong O (n) với một vài dòng mã bổ sung.
Đánh dấu Amery

2

Nếu các giá trị không phải là duy nhất và bạn hơi khó tính:

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

Đặc biệt đối với một lệnh lớn, lưu ý rằng giải pháp này kém hiệu quả hơn nhiều so với câu trả lời Python đảo ngược / đảo ngược ánh xạ vì nó lặp đi lặp lại items()nhiều lần.


7
Đây chỉ là không thể đọc được và là một ví dụ tốt về cách không viết mã duy trì. Tôi sẽ không -1vì nó vẫn trả lời câu hỏi, chỉ là ý kiến ​​của tôi.
Nga Bradberry

1

Ngoài các chức năng khác được đề xuất ở trên, nếu bạn thích lambdas:

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

Hoặc, bạn cũng có thể làm theo cách này:

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

2
-1; tất cả những gì bạn đã làm được lấy các câu trả lời khác từ trang và đưa chúng vào lambda. Ngoài ra, việc gán lambda cho một biến là vi phạm PEP 8 .
Đánh dấu Amery

1

Tôi nghĩ cách tốt nhất để làm điều này là xác định một lớp. Đây là một triển khai của một "từ điển đối xứng":

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

Phương pháp xóa và lặp lại đủ dễ thực hiện nếu cần.

Việc thực hiện này hiệu quả hơn nhiều so với việc đảo ngược toàn bộ từ điển (dường như là giải pháp phổ biến nhất trên trang này). Chưa kể, bạn có thể thêm hoặc xóa các giá trị khỏi SymDict bao nhiêu tùy ý và từ điển nghịch đảo của bạn sẽ luôn có hiệu lực - điều này không đúng nếu bạn chỉ cần đảo ngược toàn bộ từ điển một lần.


Tôi thích ý tưởng này, mặc dù sẽ tốt khi lưu ý rằng nó đánh đổi bộ nhớ bổ sung để đạt được tính toán được cải thiện. Một phương tiện hạnh phúc hơn có thể là bộ nhớ đệm hoặc lười biếng tính toán gương. Điều đáng chú ý là nó có thể được làm cho hấp dẫn hơn về mặt cú pháp với các ví dụ về chế độ xem từ điển và toán tử tùy chỉnh.
Brian M. Hunt

@ BrianM.Hunt Nó giao dịch tắt bộ nhớ, nhưng không nhiều. Bạn chỉ lưu trữ hai bộ con trỏ cho mỗi đối tượng. Nếu các đối tượng của bạn lớn hơn nhiều so với các số nguyên đơn, điều này sẽ không tạo ra nhiều sự khác biệt. Nếu bạn có một bảng lớn các vật thể nhỏ, mặt khác, bạn có thể cần xem xét các đề xuất đó ...
NcAdams

Và tôi đồng ý, có nhiều việc phải làm ở đây - tôi có thể đưa nó vào một kiểu dữ liệu đầy đủ chức năng sau này
NcAdams

2
"Việc thực hiện này hiệu quả hơn nhiều so với việc đảo ngược toàn bộ từ điển" - ừ, tại sao? Tôi không thấy bất kỳ cách hợp lý nào phương pháp này có thể có lợi ích hiệu suất đáng kể; bạn vẫn có hai từ điển theo cách này. Nếu có bất cứ điều gì, tôi hy vọng điều này sẽ chậm hơn, giả sử, đảo ngược chính tả bằng cách hiểu, bởi vì nếu bạn đảo ngược, Python có thể biết trước có bao nhiêu xô để phân bổ trong cấu trúc dữ liệu C bên dưới và tạo bản đồ nghịch đảo không bao giờ gọi dictresize, nhưng cách tiếp cận này phủ nhận khả năng đó của Python.
Mark Amery 16/07/2016 lúc

1

Điều này xử lý các giá trị không duy nhất và giữ lại nhiều giao diện của trường hợp duy nhất.

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

Đối với Python 3.x, thay thế itervaluesbằng values.


3
Giải pháp này khá thanh lịch như một lớp lót và nó quản lý trường hợp giá trị không duy nhất. Tuy nhiên, nó có độ phức tạp trong O (n2), điều đó có nghĩa là nó sẽ ổn đối với vài chục phần tử nhưng nó sẽ quá chậm để sử dụng thực tế nếu bạn có vài trăm ngàn phần tử trong từ điển ban đầu. Các giải pháp dựa trên một dict mặc định là cách nhanh hơn so với giải pháp này.
gabuzo

Gabuzo hoàn toàn đúng. Phiên bản này (được cho là) ​​rõ ràng hơn một số, nhưng nó không phù hợp với dữ liệu lớn.
Ersatz Kwisatz

0

Hàm đối xứng với các giá trị của danh sách kiểu; Các bộ dữ liệu được chuyển thành danh sách khi thực hiện 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

0

Vì từ điển yêu cầu một khóa duy nhất trong từ điển không giống như các giá trị, chúng tôi phải nối các giá trị đảo ngược vào một danh sách sắp xếp để được bao gồm trong các khóa cụ thể mới.

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

Giải pháp chức năng nhanh cho bản đồ không sinh học (giá trị không phải là duy nhất):

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

Về lý thuyết, việc này sẽ nhanh hơn việc thêm vào tập hợp (hoặc nối thêm vào danh sách) từng lượt một như trong giải pháp bắt buộc .

Thật không may, các giá trị phải được sắp xếp, việc sắp xếp được yêu cầu bởi nhóm.


1
"Về lý thuyết, việc này sẽ nhanh hơn việc thêm vào tập hợp (hoặc nối thêm vào danh sách) từng cái một" - không. Với ncác yếu tố trong dict gốc, cách tiếp cận của bạn có O(n log n)độ phức tạp về thời gian do nhu cầu sắp xếp các mục của dict, trong khi cách tiếp cận mệnh lệnh ngây thơ có O(n)độ phức tạp về thời gian. Đối với tất cả tôi biết cách tiếp cận của bạn có thể nhanh hơn cho đến lớn ngớ ngẩn dicts trong thực tế , nhưng chắc chắn nó không nhanh hơn là về mặt lý thuyết.
Mark Amery

0

Hãy thử điều này cho python 2.7 / 3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map

-1

Tôi sẽ làm theo cách đó trong python 2.

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

Lặp lại các cặp khóa-giá trị đồng thời thông qua dict.items(hoặc iteritemstrong Python 2) hiệu quả hơn so với trích xuất từng giá trị một cách riêng biệt trong khi lặp lại các khóa.
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})

Điều này sẽ cung cấp đầu ra dưới dạng: {1: ['a', 'd'], 2: ['b'], 3: ['c']}


Lặp lại các cặp khóa-giá trị đồng thời thông qua dict.items(hoặc iteritemstrong Python 2) hiệu quả hơn so với trích xuất từng giá trị một cách riêng biệt trong khi lặp lại các khóa. Ngoài ra, bạn đã thêm không có lời giải thích cho một câu trả lời trùng lặp với người khác.
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

mã này làm như thế này:

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
Nói chung, câu trả lời sẽ hữu ích hơn nhiều nếu chúng bao gồm một lời giải thích về những gì mã được dự định làm và tại sao điều đó giải quyết vấn đề mà không giới thiệu người khác.
Tom Aranda

1
Nó rất hay, nhưng rất nhiều quyết định không giải thích được (ví dụ, tại sao chữ thường cho khóa?)
Liudvikas Akelis

-2

Không phải là một cái gì đó hoàn toàn khác, chỉ là một công thức viết lại một chút từ Cookbook. Nó được tối ưu hóa hơn nữa bằng setdefaultphương pháp giữ lại , thay vì mỗi lần đưa nó qua ví dụ:

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)

Được thiết kế để chạy dưới CPython 3.x, thay thế cho 2.x mapping.items()bằngmapping.iteritems()

Trên máy của tôi chạy nhanh hơn một chút, so với các ví dụ khác ở đây


1
Xây dựng kết quả là một dictvà sau đó chuyển đổi sang lớp mong muốn ở cuối (thay vì bắt đầu với một loại đúng loại) có vẻ như tôi phát sinh một cú đánh hiệu suất hoàn toàn có thể tránh được, ở đây.
Đánh dấu Amery

-2

Tôi đã viết điều này với sự trợ giúp của chu trình 'cho' và phương thức '.get ()' và tôi đã thay đổi tên 'map' của từ điển thành 'map1' vì 'map' là một hàm.

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

-2

Nếu các giá trị không phải là duy nhất VÀ có thể là hàm băm (một chiều):

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)

Và với một đệ quy nếu bạn cần đào sâu hơn thì chỉ cần một chiều:

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)

Bạn có thể cải thiện các giải pháp của mình bằng cách sử dụng defaultdict: nó sẽ xóa tất cả các dòng invDict [item] = invDict.get (item, [])
gabuzo

Cách tiếp cận đầu tiên của bạn ở đây chuyển đổi {"foo": "bar"}thành {'b': ['foo'], 'a': ['foo'], 'r': ['foo']}và đưa ra một ngoại lệ nếu bất kỳ giá trị nào trong đó myDictkhông phải là một lần lặp. Tôi không chắc hành vi nào bạn đang cố gắng thực hiện ở đây, nhưng những gì bạn thực sự thực hiện là điều mà hầu như không ai muốn.
Đánh dấu Amery
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.