How can I ask for a key of a subkey in a dictionary in Python?

Deadly 提交于 2020-04-11 05:35:31

问题


If I have a dictionary in a dictionary, how can I ask for a key in constant time? For example:

def get_hobby(hobby):
    d = {'An' : {'Hobby': "Paintball", 'Age' : 22}, 'Jef' : {'Hobby' : "Football", 'Age': 24}, 'Jos' : {'Hobby': "Paintball", 'Age' : 46}}
assert get_hobby("Paintball") == ['An', 'Jos']

This doesn't work:

return d.keys[hobby]

回答1:


Use a list comprehension:

return [name for name, props in d.items() if props['Hobby'] == hobby]

d.items() gives you a sequence of (key, value) pairs, where the value is the nested dictionary. The list comprehension filters these by matching the hobby variable to the nested 'Hobby' key, producing a list of the names for which the filter test returns True.

You cannot ask for the keys in constant time, because that number is variable.

Demo:

>>> def get_hobby(hobby):
...     d = {'An' : {'Hobby': "Paintball", 'Age' : 22}, 'Jef' : {'Hobby' : "Football", 'Age': 24}, 'Jos' : {'Hobby': "Paintball", 'Age' : 46}}
...     return [name for name, props in d.items() if props['Hobby'] == hobby]
... 
>>> get_hobby("Paintball")
['Jos', 'An']

Note that the returned list of keys is in arbitrary order, because dictionaries have no set ordering. You cannot simply test that list against another list and expect it to be equal every single time, because lists do have order. The exact order depends on the Python hash seed and the insertion and deletion history of the dictionary.

You may want to return a set instead; sets do not have ordering either and better reflect the nature of the matching keys returned:

return {name for name, props in d.items() if props['Hobby'] == hobby}

after which your assertion would become:

assert get_hobby("Paintball") == {'An', 'Jos'}



回答2:


This should work:

return [key for key, val in d.items() if val['Hobby'] == hobby]

For example:

def get_hobby(hobby):
    d = {
        'An': {'Hobby': "Paintball", 'Age' : 22},
        'Jef': {'Hobby' : "Football", 'Age': 24},
        'Jos' : {'Hobby': "Paintball", 'Age' : 46}
    }
    return [key for key, val in d.items() if val['Hobby'] == hobby]

print get_hobby("Paintball")

Result:

['Jos', 'An']



回答3:


If you need to make lots of these queries in constant time, you have to change to an appropriate data structure. For example:

d2 = {}
for name, subdict in d.items():
    for key, value in subdict:
        d2.setdefault((key, value), set()).add(name)

(Notice that I used a set, not a list; Martijn Pieters' answer explains why.)

Now:

d2['Hobby', 'Paintball']

Simple, and efficient.

Of course building the data structure doesn't take constant time; it obviously has to iterate over every sub element of every element of your whole dict. But you only do that once, and then all of your zillion queries are constant time. So, as long as you can afford the space, and "zillion" is actually a large number, this is the optimization you want.

You will need to restructure your code so the dict actually is built once, rather than every time get_hobbies is called. Whether that means putting this in a class, using a closure, explicitly memoizing in an attribute stashed on the function, or just using a global that's built at the top level is up to you. Taking the last one, just because it's shortest (it's probably not best):

d = {'An' : {'Hobby': "Paintball", 'Age' : 22}, 'Jef' : {'Hobby' : "Football", 'Age': 24}, 'Jos' : {'Hobby': "Paintball", 'Age' : 46}}
d2 = {}
for name, subdict in d.items():
    for key, value in subdict:
        d2.setdefault((key, value), set()).add(name)

def get_hobby(hobby):
    return d2['Hobby', hobby]

assert get_hobby("Paintball") == {'An', 'Jos'}


来源:https://stackoverflow.com/questions/29930051/how-can-i-ask-for-a-key-of-a-subkey-in-a-dictionary-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!