index python dictionary by value [duplicate] - python

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Inverse dictionary lookup - Python
Is there a built in way to index a dictionary by value in Python.
e.g. something like:
dict = {'fruit':'apple','colour':'blue','meat':'beef'}
print key where dict[key] == 'apple'
or:
dict = {'fruit':['apple', 'banana'], 'colour':'blue'}
print key where 'apple' in dict[key]
or do I have to manually loop it?

You could use a list comprehension:
my_dict = {'fruit':'apple','colour':'blue','meat':'beef'}
print [key for key, value in my_dict.items() if value == 'apple']
The code above is doing almost exactly what said you want:
print key where dict[key] == 'apple'
The list comprehension is going through all the key, value pairs given by your dictionary's items method, and making a new list of all the keys where the value is 'apple'.
As Niklas pointed out, this does not work when your values could potentially be lists. You have to be careful about just using in in this case since 'apple' in 'pineapple' == True. So, sticking with a list comprehension approach requires some type checking. So, you could use a helper function like:
def equals_or_in(target, value):
"""Returns True if the target string equals the value string or,
is in the value (if the value is not a string).
"""
if isinstance(target, str):
return target == value
else:
return target in value
Then, the list comprehension below would work:
my_dict = {'fruit':['apple', 'banana'], 'colour':'blue'}
print [key for key, value in my_dict.items() if equals_or_in('apple', value)]

You'll have to manually loop it, but if you'll need the lookup repeatedly this is a handy trick:
d1 = {'fruit':'apple','colour':'blue','meat':'beef'}
d1_rev = dict((v, k) for k, v in d1.items())
You can then use the reverse dictionary like this:
>>> d1_rev['blue']
'colour'
>>> d1_rev['beef']
'meat'

Your requirements are more complex than you realize:
You need to handle both list values and plain values
You don't actually need to get back a key, but a list of keys
You could solve this in two steps:
normalize the dict so that every value is a list (every plain value becomes a single-element)
build a reverse dictionary
The following functions will solve this:
from collections import defaultdict
def normalize(d):
return { k:(v if isinstance(v, list) else [v]) for k,v in d.items() }
def build_reverse_dict(d):
res = defaultdict(list)
for k,values in normalize(d).items():
for x in values:
res[x].append(k)
return dict(res)
To be used like this:
>>> build_reverse_dict({'fruit':'apple','colour':'blue','meat':'beef'})
{'blue': ['colour'], 'apple': ['fruit'], 'beef': ['meat']}
>>> build_reverse_dict({'fruit':['apple', 'banana'], 'colour':'blue'})
{'blue': ['colour'], 'apple': ['fruit'], 'banana': ['fruit']}
>>> build_reverse_dict({'a':'duplicate', 'b':['duplicate']})
{'duplicate': ['a', 'b']}
So you just build up the reverse dictionary once and then lookup by value and get back a list of keys.

Related

Comparing the strings in key and value of the same dictionary

I am looking to solve a problem to compare the string of the key and value of the same dictionary.
To return a dictionary of all key and values where the value contains the key name as a substring.
a = {"ant":"antler", "bi":"bicycle", "cat":"animal"}
the code needs to return the result:
b = {"ant":"antler", "bi":"bi cycle"}
You can iterate through the dictionary and unpack the key and the value at the same time this way:
b = {}
for key, value in a.items():
if value in key:
b[value] = key
This will generate your wanted solution. It does that by unpacking both the key and the value and checking if they match afterward.
You can also shorten that code by using a dictionary comprehension:
b = {key:value for key, value in a.items() if key in value}
This short line does the exact same thing as the code before. It even uses the same functionalities with only one addition - a dictionary comprehension. That allows you to put all that code in one simple line and declare the dictionary on the go.
answer = {k:v for k,v in a.items() if k in v}
Notes:
to iterate over key: value pair we use dict.items();
to check if a string is inside some other string we use in operator;
to filter items we use if-clause in the dictionary comprehension.
See also:
about dictionary comprehensions
about operators in and not in

Convert all keys in dictionary to lower case with dictionary comprehension

I wrote a function to convert all keys in a dictionary to lowercase:
def lower_dict_keys(some_dict):
"""Convert all keys to lowercase"""
result = {}
for key, value in some_dict.items():
if type(key) == str:
result[key.lower()] = value
else:
result[key] = value
return result
So far so good. Then I thought, hm, this would be more elegant in a dictionary comprehension, so this is what I came up with:
def lower_dict_keys2(some_dict):
return {key.lower(): value for key, value in some_dict.items() if type(key) == str}
This only works if all keys are strings. I there are numeric keys, they get dropped:
d1 = {'A':'foo', 'b':'bar', 1:'zip'}
print(lower_dict_keys(d1))
>>>{'a': 'foo', 'b': 'bar', 1: 'zip'}
print(lower_dict_keys2(d1))
>>>{'a': 'foo', 'b': 'bar'}
So my question is: is it possible to write this function as a dictionary comprehension?
The correct translation of your code to a comprehension is:
{key.lower() if type(key) == str else key: value for key, value in some_dict.items()}
using a conditional expression to determine the key for each key-value pair.
Using if after the for loop filters on elements; those elements that don't pass the if test are not included in the output.
You also want to avoid using type(...) is, use isinstance() instead; subclasses of str will support the lower() method too:
{key.lower() if isinstance(key, str) else key: value for key, value in some_dict.items()}
If you expect your keys to ordinarily be strings, a more appropriate solution would be to use a try / except clause.
Here we catch AttributeError, which will occur only for non-string elements.
def lower_dict_keys(some_dict):
"""Convert all keys to lowercase"""
result = {}
for key, value in some_dict.items():
try:
result[key.lower()] = value
except AttributeError:
result[key] = value
return result
d1 = {'A':'foo', 'b':'bar', 1:'zip'}
lower_dict_keys(d1)
# {'a': 'foo', 'b': 'bar', 1: 'zip'}
Unfortunately, this will not work with a dictionary comprehension, since statements are not permitted in comprehensions and PEP-463 was rejected.

Best way to check if value in dictionary of lists?

So I have a dictionary of lists like so:
dct = {'1': ['hello','goodbye'], '2': ['not here','definitely not here']}
What's the fastest way to check if 'hello' is in one of my lists in my dictionary
As Willem Van Onsem commented, the easiest way to achieve this is:
any('hello' in val for val in dct.values())
any returns True if any of the values in the given iterable are truthy.
dct.values() returns a dict_values iterable that yields all the values in a dictionary.
'hello' in val for val in dct.values() is a generator expression that yields True for each value of dct that 'hello' is in.
If you want to know the keys the string is in, you can do:
keys = [key for key, value in dct.items() if 'hello' in value]
In your case, keys will be ['1']. If you do this anyways, you can then just call use that list in a boolean context, e.g. if keys: ....

Merging values from 2 dictionaries (Python)

(I'm new to Python!)
Trying to figure out this homework question:
The function will takes a​s input​ two dictionaries, each mapping strings to integers. The function will r​eturn​ a dictionary that maps strings from the two input dictionaries to the sum of the integers in the two input dictionaries.
my idea was this:
def ​add(​dicA,dicB):
dicA = {}
dicB = {}
newdictionary = dicA.update(dicB)
however, that brings back None.
In the professor's example:
print(add({'alice':10, 'Bob':3, 'Carlie':1}, {'alice':5, 'Bob':100, 'Carlie':1}))
the output is:
{'alice':15, 'Bob':103, 'Carlie':2}
My issue really is that I don't understand how to add up the values from each dictionaries. I know that the '+' is not supported with dictionaries. I'm not looking for anyone to do my homework for me, but any suggestions would be very much appreciated!
From the documentation:
update([other])
Update the dictionary with the key/value pairs from other, overwriting existing keys. Return None.
You don't want to replace key/value pairs, you want to add the values for similar keys. Go through each dictionary and add each value to the relevant key:
def ​add(​dicA,dicB):
result = {}
for d in dicA, dicB:
for key in d:
result[key] = result.get(key, 0) + d[key]
return result
result.get(key, 0) will retrieve the value of an existing key or produce 0 if key is not yet present.
First of all, a.update(b) updates a in place, and returns None.
Secondly, a.update(b) wouldn't help you to sum the keys; it would just produce a dictionary with the resulting dictionary having all the key, value pairs from b:
>>> a = {'alice':10, 'Bob':3, 'Carlie':1}
>>> b = {'alice':5, 'Bob':100, 'Carlie':1}
>>> a.update(b)
>>> a
{'alice': 5, 'Carlie': 1, 'Bob': 100}
It'd be easiest to use collections.Counter to achieve the desired result. As a plus, it does support addition with +:
from collections import Counter
def add(dicA, dicB):
return dict(Counter(dicA) + Counter(dicB))
This produces the intended result:
>>> print(add({'alice':10, 'Bob':3, 'Carlie':1}, {'alice':5, 'Bob':100, 'Carlie':1}))
{'alice': 15, 'Carlie': 2, 'Bob': 103}
The following is not meant to be the most elegant solution, but to get a feeling on how to deal with dicts.
dictA = {'Alice':10, 'Bob':3, 'Carlie':1}
dictB = {'Alice':5, 'Bob':100, 'Carlie':1}
# how to iterate through a dictionary
for k,v in dictA.iteritems():
print k,v
# make a new dict to keep tally
newdict={}
for d in [dictA,dictB]: # go through a list that has your dictionaries
print d
for k,v in d.iteritems(): # go through each dictionary item
if not k in newdict.keys():
newdict[k]=v
else:
newdict[k]+=v
print newdict
Output:
Bob 3
Alice 10
Carlie 1
{'Bob': 3, 'Alice': 10, 'Carlie': 1}
{'Bob': 100, 'Alice': 5, 'Carlie': 1}
{'Bob': 103, 'Alice': 15, 'Carlie': 2}
def ​add(​dicA,dicB):
You define a function that takes two arguments, dicA and dicB.
dicA = {}
dicB = {}
Then you assign an empty dictionary to both those variables, overwriting the dictionaries you passed to the function.
newdictionary = dicA.update(dicB)
Then you update dicA with the values from dicB, and assign the result to newdictionary. dict.update always returns None though.
And finally, you don’t return anything from the function, so it does not give you any results.
In order to combine those dictionaries, you actually need to use the values that were passed to it. Since dict.update mutates the dictionary it is called on, this would change one of those passed dictionaries, which we generally do not want to do. So instead, we use an empty dictionary, and then copy the values from both dictionaries into it:
def add (dicA, dicB):
newDictionary = {}
newDictionary.update(dicA)
newDictionary.update(dicB)
return newDictionary
If you want the values to sum up automatically, then use a Counter instead of a normal dictionary:
from collections import Counter
def add (dicA, dicB):
newDictionary = Counter()
newDictionary.update(dicA)
newDictionary.update(dicB)
return newDictionary
I suspect your professor wants to achieve this using more simple methods. But you can achieve this very easily using collections.Counter.
from collections import Counter
def add(a, b):
return dict(Counter(a) + Counter(b))
Your professor probably wants something like this:
def add(a, b):
new_dict = copy of a
for each key/value pair in b
if key in new_dict
add value to value already present in new_dict
else
insert key/value pair into new_dict
return new_dict
You can try this:
def add(dict1, dict2):
return dict([(key,dict1[key]+dict2[key]) for key in dict1.keys()])
I personally like using a dictionary's get method for this kind of merge:
def add(a, b):
result = {}
for dictionary in (a, b):
for key, value in dictionary.items():
result[key] = result.get(key, 0) + value
return result

How to append a value to a not yet existing key?

I have a dictionary like this:
dct = {'one': 'value',
'two': ['value1','value2','value1'],
'three':['otherValue1','otherValue2','otherValue1'],
'dontCareAboutThisKey':'debug'}
I need to remove duplicate values from the lists. I wrote a function to do this:
no_dups = {}
for keys in dct:
if isinstance(dct[keys], list) and keys != 'dontCareAboutThisKey':
for value in dct[keys]:
if value not in no_dups.values():
no_dups[keys].append(value)
else:
no_dups[keys] = dct[keys]
I'm checking if value of the current key is a list. If no, it just 'copy' key to no_dups dictionary. If it is a list and not a key that I don't care about (there are no duplicates for sure) - it should check if current value already exists in no_dups.values() and append it to current key. Problem is that I'm getting an error:
KeyError: 'two:'
I know it's because I'm trying to add a value to non existing key but I have no idea how to deal with this and make it work.
I think the best way to deal with adding the key and appending at the same time is with dicts' setdefault() method:
no_dups.setdefault(keys,[]).append(value)
But rather than that, you can do this in a more neat way like this:
#remove duplicates
no_dups = {k:list(set(v)) if isinstance(v, list) and k != 'dontCareAboutThisKey' else v
for k,v in dct.items()} # or dct.iteritems() if using python2.x
That hack will, for key value combinations that pass the if test, convert the list into a set (removing duplicates) and then in a list again. For other key value combinations it will leave it intact.
dct = {'one': 'value',
'two': ['value1','value2','value1'],
'three':['otherValue1','otherValue2','otherValue1'],
'dontCareAboutThisKey':'debug'}
set(dct) returns a set, which is a list without duplicates:
for key, value in dct.items():
if not isinstance(value, basestring):
dct[key] = set(value)
If you need a new dictionary you could do:
new_dct = {}
for key, value in dct.items():
if not isinstance(value, basestring):
new_dct[key] = set(value)
else:
new_dct[key] = value
If You want to remove duplicates, just change You list to set, with set() function:
https://docs.python.org/2/tutorial/datastructures.html#sets
It automatically gives You unique set, then You can always change it back to list.

Categories

Resources