1

I have a dictionary like this:

a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}

I would like to subtract the sum of the corresponding elements of the nested lists in the values from each element of each key, and replace the key with the result.

For example:

new_key[0] = 8 - 0+4 = 4, new_key[1] = 9 - (0+5) = 4

Hence the new key becomes (4, 4) and it replaces (8, 9)

I am not able to understand how to access a list of lists which is the value to the key!

Any ideas as to how to do this?

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Ashutosh Mishra
  • 183
  • 1
  • 1
  • 10
  • don't forget about operator precedence, probably you mean `8 - (0+4)` – kilgoretrout Oct 29 '18 at 05:18
  • Also the new key is (4,4) not (4,5) – b-fg Oct 29 '18 at 05:22
  • As @Mad Physicist pointed out, you should think about what should happen if your formula gives the same key more than once – kilgoretrout Oct 29 '18 at 05:51
  • "...how to access a list of lists which is the value to the key"-- see https://stackoverflow.com/questions/6473679/transpose-list-of-lists and also my full answer below. – Nic Oct 31 '18 at 23:00

6 Answers6

4

See Access item in a list of lists for indexing list of list.

For your specific case, this should work

b = {(k[0]-v[0][0]-v[1][0], k[1]-v[0][1]-v[1][1]):v for k, v in a.items()}
klim
  • 1,179
  • 8
  • 11
1
for key in list(a.keys()):
    new_key = []
    new_key.append(key[0] - (a[key][0][0] + a[key][1][0]))
    new_key.append(key[1] - (a[key][0][1] + a[key][1][1]))
    a[new_key] = a.pop(key) # pop() returns the value
kilgoretrout
  • 3,547
  • 5
  • 31
  • 46
  • #1, you're modifying keys while iterating (use `list (a.keys())` to avoid this) #2, you could end up overwriting an unprocessed key unintentionally. – Mad Physicist Oct 29 '18 at 05:20
  • #1 is fair but OP didn't specify what to do in the case of #2 so not clear how to handle it – kilgoretrout Oct 29 '18 at 05:22
1

Iterate through the dictionary to get the keys and values and create a new one b. Then just point a to the new dictionary b

a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}
b = {}
for key, val in a.items():
    new_key = (key[0]-(val[0][0]+val[1][0]), key[1]-(val[0][1]+val[1][1]))
    b[new_key] = val
a = b
del b
b-fg
  • 3,959
  • 2
  • 28
  • 44
1

Try This:

b = {}
for key, value in a.items():
    new_key = key[0]-(value[0][0]+value[1][0])
    new_key_1 = key[1]-(value[0][1]+value[1][1])
    u_key = (new_key, new_key_1)
    b[u_key]=value
print(b)
Rohit-Pandey
  • 2,039
  • 17
  • 24
0

The following code will work just as well with any size of key tuple (2, 5, 87, whatever.)

There is no simple way to rename a dictionary key, but you can insert a new key and delete the old one. This isn't recommended for a couple of reasons:

If you need a dictionary result, the safest thing is to generate an entirely new dictionary based on a, as has been done here.

The problem you're trying to solve is easier if you transpose the dictionary values.

After calculating the new key, (8, 9): [[0, 0], [4, 5]] should become:

(8 - sum([0, 4]), 9 - sum([0, 5])): [[0, 0], [4, 5]] 

Now see how transposing helps:

transposed([[0, 0], [4, 5]]) == [[0, 4], [0, 5]]

then the new key[0] calculation is:

key[0] - sum(transposed(values)[0])

and the new key[1] calculation is:

key[1] - sum(transposed(values)[1])

So transposing makes the calculation easier.

Python dictionaries can't have lists as keys (lists are not hashable) so I've built the key as a list, then converted it to a tuple at the end.

a = {
    (8, 9): [[0, 0], [4, 5]],
    (3, 4): [[1, 2], [6, 7]]
}

def transpose(m):
    return list(zip(*m))

results = {}
for source_keys, source_values in a.items():
    transposed_values = transpose(source_values)
    key = []
    for n, key_item in enumerate(source_keys):
        subtractables = sum(transposed_values[n])
        key.append(key_item - subtractables)
    results[tuple(key)] = source_values

print(results)

>>> python transpose.py
{(4, 4): [[0, 0], [4, 5]], (-4, -5): [[1, 2], [6, 7]]}
Nic
  • 1,518
  • 12
  • 26
-1

The following solution works with three assumptions:

  1. The keys are iterables of integers
  2. The values are iterables of iterables of integers
  3. The inner iterables of each value are the same length as the corresponding key

Practically, this means that the length of the value can be anything, as long as it's a 2D list, and that you can have keys of different lengths, even in the same dictionary, as long as the values match along the inner dimension.

You would want to transpose the values to make the sum easier to compute. The idiom zip(*value) lets you do this quite easily. Then you map sum onto that and subtract the result from the elements of the key.

Another thing to keep in mind is that replacing keys during iteration is a very bad idea. You're better off creating an entirely new dictionary to hold the updated mapping.

from operator import sub
from itertools import starmap

a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}
b = {
    tuple(starmap(sub, zip(k, map(sum, zip(*v))))): v
    for k, v in a.items()
}

The result is

{(4, 4): [[0, 0], [4, 5]], (-4, -5): [[1, 2], [6, 7]]}

Here is an IDEOne Link to play with.

The full version of the loop would look like this:

b = {}
for k, v in a.items():
    vt = zip(*v)  # transposed values
    sums = map(sum, vt)  # sum of the 1st elements, 2nd elements, etc
    subs = zip(k, sums)  # match elements of key with sums to subtract
    diffs = starmap(sub, subs)  # subtract sums from key
    new_key = tuple(diffs)  # evaluate the generators
    b[new_key] = value
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • 3
    Did you just deleted the answer and reposted it to get rid of the downvotes? – b-fg Oct 29 '18 at 05:27
  • @b-fg. I added significant edits to it. Why the downvotes? – Mad Physicist Oct 29 '18 at 05:34
  • @user3483203. Yes, that was part of the idea. Why the downvotes? The code works. My explanation is fairly detailed. I'm really curious. And yes, deleting and reposting was pretty uncool. – Mad Physicist Oct 29 '18 at 05:36
  • It's way overkill for someone who is clearly new to python. – kilgoretrout Oct 29 '18 at 05:37
  • You just added an explanation to be honest. The downvotes, at least mine, is for using an overcomplicated method. If you are downvoted and you want to take the answer down, that's okay. But do not repost to just get rid of that. A new solution, a new answer. And it's not your case. Hence my comment. – b-fg Oct 29 '18 at 05:37
  • @b-fg. Fair enough – Mad Physicist Oct 29 '18 at 05:38
  • 1
    @kilgoretrout. On the one hand yes, but on the other hand SO is for future readers as much as for OP. That's part of the site's started purpose. – Mad Physicist Oct 29 '18 at 05:39
  • The question is very basic. No one who needs the answer to that question needs a complicated answer. It just confuses them, makes them feel stupid, and could cause more damage than help. It's not the end of the world but this isn't helpful to someone getting started. You're clearly an advanced engineer but two library imports and three code blocks is not necessary here - for anyone. – kilgoretrout Oct 29 '18 at 05:43
  • I agree with Mad Physicist. It never hurts to learn more. I up vote the answer. – Milo Lu Oct 29 '18 at 05:46
  • 1
    @kilgoretrout. That makes perfect sense. I had fun turning this into a pretty general one-liner, so I'll leave it here, with the understanding that what you and \@bf-g are telling me is accepted. – Mad Physicist Oct 29 '18 at 05:47
  • @MiloLu. I appreciate that. While I actually agree with the downvotes, my answer does offer a more generic solution. – Mad Physicist Oct 29 '18 at 05:50