简体   繁体   中英

reverse keys (consisting of lists) and values in Python dictionary

I have been trying to figure this out from other posts here, but couldn't.

I have a Python dictionary

old_dict = { (1,'a') : [2],
          (2,'b') : [3,4],
          (3,'x') : [5],
          (4,'y') : [5],
          (5,'b') : [3,4], 
          (5,'c') : [6],
          }

I need to reverse this so that as a result I would have:

new_dict = { (6,'c') : [5],
          (5,'x') : [3],
          (5,'y') : [4],
          (4,'b') : [5, 2],
          (3,'b') : [5, 2], 
          (2,'a') : [1],
          }

(This describes the edges of a finite state machine, and I need to run it backwards: it has to accept the reverse inputs as it would have before)

For instance, in old_dict, the first key was a list (1, 'a') : [2] , and now, this one should become (2, 'a'), [1] ... or (4,'y') : [5] becomes (5,'y') : [4] etc. - I hope it is understandable what I mean.

I have been trying to solve this with list comprehensions, but no success yet.

Update: I tried FC 's suggestion, but somehow I can't get the code to work. I inserted it into a function, like so:

old_dict1 = { (1,'a') : [2],
          (2,'b') : [3,4],
          (3,'x') : [5],
          (4,'y') : [5],
          (5,'b') : [3,4], 
          (5,'c') : [6],
          }

def reverse_dict(old_dict):
    new_dict = {}
    add_to_dict = new_dict.setdefault

    map(lambda kv: add_to_dict(kv[0], []).append(kv[1]),   
        sum([[((x, k[1]), k[0]) for x in v] for k, v in old_dict.items()],
            []))        # sum will take this to start adding
    return new_dict

new_dict1 = reverse_dict(old_dict1)

print(new_dict1)

But I only get returned an empty dictionary {}

Am I doing something wrong ? (I have really very little knowledge of Python, so please forgive me if I made a mistake that's too silly ...)

This is complex enough that I wouldn't bother with list comprehensions. Also, I'm assuming you aren't looking for the value lists to be in any strict order.

new_dict = {}
for k, vals in old_dict.items():
    k_num, k_char = k
    for num in vals:
        new_dict.setdefault((num, k_char), []).append(k_num)

Or using a defaultdict :

new_dict = collections.defaultdict(list)
for k, vals in old_dict.items():
    k_num, k_char = k
    for num in vals:
        new_dict[(num, k_char)].append(k_num)

For those interested in being as terse as possible, it occurs to me that this more compressed version is an option as well. I'm not sure how I feel about this from a readability standpoint, so I changed the variable names for a bit more clarity:

new_dict = collections.defaultdict(list)
for (num_in, char_in), nums_out in old_dict.items():
    for num_out in nums_out:
        new_dict[(num_out, char_in)].append(num_in)

This works with your data, take it as an ugly hack just for fun .

It would be better to do it in more lines of code so it can be easier to understand but sometimes I can't resist the temptation to write this contraptions.

Hope it helps.

def reverse_dict(old_dict):
    """
    >>> sorted(reverse_dict({(1,'a'): [2],
    ...               (2,'b'): [3,4],
    ...               (3,'x'): [5],
    ...               (4,'y'): [5],
    ...               (5,'b'): [3,4], 
    ...               (5,'c'): [6],
    ...              }).items())
    [((2, 'a'), [1]), ((3, 'b'), [2, 5]), ((4, 'b'), [2, 5]), ((5, 'x'), [3]), ((5, 'y'), [4]), ((6, 'c'), [5])]
    """
    new_dict = {}
    add_to_dict = new_dict.setdefault       # you could use a [defaultdict][1] instead

    map(lambda kv: add_to_dict(kv[0], []).append(kv[1]),   # if kv[0] not in dict get [] and add to it
        sum([[((x, k[1]), k[0]) for x in v] for k, v in old_dict.items()],
            []))        # sum will take this to start adding
    return new_dict

To test the code just copy it to a file so.py and run it like this:

$ python -m doctest so.py -v
Trying:
    sorted(reverse_dict({(1,'a'): [2],
                  (2,'b'): [3,4],
                  (3,'x'): [5],
                  (4,'y'): [5],
                  (5,'b'): [3,4], 
                  (5,'c'): [6],
                 }).items())
Expecting:
    [((2, 'a'), [1]), ((3, 'b'), [2, 5]), ((4, 'b'), [2, 5]), ((5, 'x'), [3]), ((5, 'y'), [4]), ((6, 'c'), [5])]
ok
1 items had no tests:
    so
1 items passed all tests:
   1 tests in so.reverse_dict
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

It uses doctest to make it easier to test if its doing what you want.

I think that there is a problem in your understanding of the purpose of a dict . The dict data structure shouldn't be thought of as being ordered in any particular way since it uses a hash table for accessing the elements. You should read the docs and the note on the implementation detail in .items() here is also important. It turns out that the implementation may give you the order that you seem to be expecting, but you shouldn't count on it.

If the order is important to you, then you should use a list in at least the part of your code where the order is important. Use the .items() method on your dict to get the list of (key,value) pairs, then you can sort them any way you want using the usual sorting methods on lists.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM