2

I'm working with a nested dictionary and I'm trying to figure out how to modify certain nested and non-nested keys/values effectively. In short, I'm trying to do the following:

  1. take a nested value and switch it with a non-nested key
  2. rename a nested key.

Here's a basic example of a dictionary that I'm working with:

pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3', 
                  'breed':'bulldog'}, 
                  'Henley':{'color':'blue', 'weight': 70, 'age':'2', 
                  'breed':'bulldog'}, 
                  'Emi':{'color':'lilac', 'weight': 65, 'age':'1', 
                  'breed':'bulldog'}, 
                  }

I want to take the non-nested key, which is name of each dog (i.e. Buford, Henley, Emi), switch it with nested value for the age (i.e. 3, 2, 1), and then change the nested key name from 'age' to 'name.' So the output should look like this:

pet_dictionary = {'3':{'color':'white', 'weight': 95, 'name':'Buford', 
                  'breed':'bulldog'}, 
                  '2':{'color':'blue', 'weight': 70, 'name':'Henley', 
                  'breed':'bulldog'}, 
                  '1':{'color':'lilac', 'weight': 65, 'name':'Emi', 
                  'breed':'bulldog'}, 
                  }

I understand how to do this manually one-by-one, but I'm not sure what the best approach is for making all of these changes in a more elegant/optimal way.

1
  • 3
    Note that, conceptually, you can’t change a key in a dictionary: if you modify the original at all, it must be by removing the old key-value association and adding a new one (with the same value). Commented Jan 31, 2018 at 4:08

9 Answers 9

2

While iterating your dictionary, you can cleanly build a new dictionary in three steps:

# Preserves original dict
d = {}
for k, v in pet_dictionary.items():                     
    key = v["age"]                                                 # 1. grab the new key
    d[key] = {"name": k}                                           # 2. add new "name" item
    d[key].update({k_:v_ for k_, v_ in v.items() if k_!="age"})    # 3. update the new dict

d
Sign up to request clarification or add additional context in comments.

Comments

2

This might help

pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
                  'breed':'bulldog'},
                  'Henley':{'color':'blue', 'weight': 70, 'age':'2',
                  'breed':'bulldog'},
                  'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
                  'breed':'bulldog'},
                  }

d = {}
for k,v in pet_dictionary.items():
    d[v['age']] = pet_dictionary[k]
    d[v['age']].update({"name": k})
    del d[v['age']]['age']

print d

Output:

{'1': {'color': 'lilac', 'breed': 'bulldog', 'name': 'Emi', 'weight': 65}, '3': {'color': 'white', 'breed': 'bulldog', 'name': 'Buford', 'weight': 95}, '2': {'color': 'blue', 'breed': 'bulldog', 'name': 'Henley', 'weight': 70}}

1 Comment

I think you can shorten d[pet_dictionary[k]['age']] with d[v["age"]]
2

This is a situation where pythons iterables come to shine

p = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
                  'breed':'bulldog'},
                  'Henley':{'color':'blue', 'weight': 70, 'age':'2',
                  'breed':'bulldog'},
                  'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
                  'breed':'bulldog'},
                  }

new_dictionary = {p[i]['age']:{'color':p[i]['color'],'weight':p[i]['weight'],
                    'name':i,'breed':p[i]['breed']} for i in p}

Output:

{'3': {'color': 'white', 'weight': 95, 'name': 'Buford', 'breed': 'bulldog'},
'2': {'color': 'blue', 'weight': 70, 'name': 'Henley', 'breed': 'bulldog'},
'1': {'color': 'lilac', 'weight': 65, 'name': 'Emi', 'breed': 'bulldog'}}

3 Comments

You mention iterators. What are you referring to in particular?
Python dictionaries are iterables, which means you can use the {i for i in dict} syntax like I did above!
True. You are using a dictionary comprehension, which is iterable, not an iterator. These are easy to mix up. Here's a quick test: an iterator must be usable with iter() and next(). If you try next(new_dictionary) you'll see TypeError: 'dict' object is not an iterator. So you'll have to edit your first line. Otherwise, nice work.
1

With a couple of comprehensions, you can do that transformation like:

Code:

new_dict = {
    info['age']: {k: v for k, v in list(info.items()) + [('name', name)]
                  if k != 'age'}
    for name, info in pet_dictionary.items()
}

Test Code:

pet_dictionary = {
    'Buford': {'color': 'white', 'weight': 95, 'age': '3', 'breed': 'bulldog'},
    'Henley': {'color': 'blue', 'weight': 70, 'age': '2', 'breed': 'bulldog'},
    'Emi': {'color': 'lilac', 'weight': 65, 'age': '1', 'breed': 'bulldog'},
}

new_dict = {
    info['age']: {k: v for k, v in list(info.items()) + [('name', name)]
                  if k != 'age'}
    for name, info in pet_dictionary.items()
}

for dog in new_dict.items():
    print(dog)

Results:

('3', {'color': 'white', 'weight': 95, 'breed': 'bulldog', 'name': 'Buford'})
('2', {'color': 'blue', 'weight': 70, 'breed': 'bulldog', 'name': 'Henley'})
('1', {'color': 'lilac', 'weight': 65, 'breed': 'bulldog', 'name': 'Emi'})

Comments

1

With pandas,

import pandas as pd

pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3', 
                'breed':'bulldog'}, 
                'Henley':{'color':'blue', 'weight': 70, 'age':'2', 
                'breed':'bulldog'}, 
                'Emi':{'color':'lilac', 'weight': 65, 'age':'1', 
                'breed':'bulldog'}, 
                }
pd.DataFrame.from_dict(pet_dictionary, orient='index') \
  .reset_index() \
  .rename(columns={'index': 'name'}) \
  .set_index('age') \
  .to_dict('index')

Comments

1
def flip(k, v):
   v1 = dict(v)
   v1.update(name=k)
   return v1.pop('age'), v1

pet_dictionary2 = dict([flip(k, v) for k, v in pet_dictionary.items()])


# import pprint as pp; pp.pprint(pet_dictionary2)
# {'1': {'breed': 'bulldog', 'color': 'lilac', 'name': 'Emi', 'weight': 65},
#  '2': {'breed': 'bulldog', 'color': 'blue', 'name': 'Henley', 'weight': 70},
#  '3': {'breed': 'bulldog', 'color': 'white', 'name': 'Buford', 'weight': 95}}

If it is ok to change the previous dictionary, then you can do:

def flip(k, v):
    v.update(name=k)
    return v.pop('age'), v

Comments

1

Doing this in few lines, without any additional libs, but mutating original dictionary:

pet_dictionary = {
    nested.pop('age'): nested.update({'name': name}) or nested 
    for name, nested in pet_dictionary.items()
 }

And with additional import, but without mutating pet_dictionary:

import copy

new_pet_dict = {
    nested.pop('age'): nested.update({'name': name}) or nested 
    for name, nested in copy.deepcopy(pet_dictionary).items()
}

...which leaves original pet_dictionary untouched.

Info

Initially, I published different answer, where key in new dict where created using .pop method, and nested dict using {**nested, 'name': name} but it didn't work. It would be much cleaner solution, but AFAIK, interpreter reads code from right to left and... that's obviously wouldn't work using this approach.

How does this work then? It looks little tricky, especially line:

nested.update({'name': name}) or nested

But let's have a closer look. We need nested to be updated with name key, but that returns None and mutates object. So left part of this or will be always None and we would like to have dict object in our dict comprehension. Here comes short circuit evaluation in Python, which always returns second operand if first is falsy.

None-mutating example uses deepcopy and mutates ad-hoc copy, not original dictionary.

Comments

1

Update for Python 3.8:

Only if mutating original dict is acceptable (credits to @pylang for noticing it), there is neat syntax for playing with dictionaries:

new = {nested.pop('age'): {**nested, 'name': name} for name, nested in pet_dictionary.items()}

3 Comments

This is the way to do it. Great work. Only issue is mutating the original dict, if that is acceptible.
A non-destructive way might be to use another comprehension new = {nested['age']: {**{k:v for k,v in nested if k!="age"} for name, nested in pet_dictionary.items()}
Well, I sometimes prefer solutions like yours with explicit loop as more clear. And yes, it is acceptable only if previous dict wouldn't be used anymore. If it would be, I’ll go for your initial solution with for loop, as nested comprehension are far less readable.
0

Here's a two liner:

##add new value, which was formerly the key
{k: v.update({'name':k}) for k,v in pet_dictionary.items()}
##reset keys to value of 'age'
new_pet = {v.get('age'): v for k,v in pet_dictionary.items()}

1 Comment

First: mutating v is not beneficial in your first comprehension, this creates dict with None values for no reason. Second: age is still key in your new_pet dict (you can fix it using pop instead of get).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.