I would like to check the intersection of two dictionaries. If I do this, I get exactly what I expected:
dict1 = {'x':1, 'y':2, 'z':3}
dict2 = {'x':1, 'y':2, 'z':4}
set(dict1.items()).intersection(dict2.items())
>> {('x', 1), ('y', 2)}
However, if the items within the dictionary are nonhashable, I get an error.
dict1 = {'x':{1,2}, 'y':{2,3}, 'z':3}
dict2 = {'x':{1,3}, 'y':{2,4}, 'z':4}
TypeError Traceback (most recent call
last)
<ipython-input-56-33fdb931ef54> in <module>
----> 1 set(dict1.items()).intersection(dict2.items())
TypeError: unhashable type: 'set'
Of course, I get the same error for tuples and lists, as they are also not hashable.
Is there a work around or an existing class I can use to check the intersection of nonhashable dictionary values?
You could create a "makeHashable" function to apply to dictionary items for comparison purposed and used it to build a set that you can then check in a list comprehension:
dict1 = {'x':{1,2}, 'y':{2,3}, 'z':3}
dict2 = {'x':{1,3}, 'y':{3,2}, 'z':4}
def makeHashable(x):
if isinstance(x,(list,tuple)): return tuple(map(makeHashable,x))
if isinstance(x,set): return makeHashable(sorted(x))
if isinstance(x,dict): return tuple(map(makeHashable,x.items()))
return x
dict1Set = set(map(makeHashable,dict1.items()))
intersect = [ kv for kv in dict2.items() if makeHashable(kv) in dict1Set]
output:
print(intersect)
# [('y', {2, 3})]
Maybe try:
#!/usr/local/cpython-3.8/bin/python3
def intersection1(dict1, dict2):
intersection = set(dict1.items()).intersection(dict2.items())
return intersection
def intersection2(dict1, dict2):
result = {}
for key1 in dict1:
if key1 in dict2 and dict1[key1] == dict2[key1]:
result[key1] = dict1[key1]
return result
def main():
dict1 = {'x': 1, 'y': 2, 'z': 3}
dict2 = {'x': 1, 'y': 2, 'z': 4}
print(intersection2(dict1, dict2))
print(intersection1(dict1, dict2))
# >> {('x', 1), ('y', 2)}
dict3 = {'x': [1, 2], 'y': [2, 3], 'z': [3, 4]}
dict4 = {'x': [1, 2], 'y': [2, 3], 'z': [4, 5]}
print(intersection2(dict3, dict4))
print(intersection1(dict3, dict4))
main()
You of course cannot put an unhashable type in a set, so I've done the next best thing with intersection2()
You can serialize the dict values before performing set intersection, and deserialize the values in the resulting set. The following example uses pickle for serialization:
import pickle
{k: pickle.loads(v) for k, v in set.intersection(
*({(k, pickle.dumps(v)) for k, v in i} for i in map(dict.items, (dict1, dict2))))}
so that given:
dict1 = {'x': {1, 2}, 'y': {2, 3}, 'z': 3}
dict2 = {'x': {2, 1}, 'y': {2, 4}, 'z': 4}
the expression would return:
{'x': {1, 2}}
Related
I had some problems with the code that was given in an answer at this post:
Can I use a nested for loop for an if-else statement with multiple conditions in python?
import pprint
board = {
'1a': 'bking',
'4e': 'bpawn',
'2c': 'bpawn',
'3f': 'bpawn',
'5h': 'bbishop',
'6d': 'wking',
'7f': 'wrook',
'2b': 'wqueen'
}
count = {}
for k, v in board.items():
count[k[0]][k[1:]] = v
pprint.pprint(count)
I wanted to get the following dictionary:
count = {'b': {'king': 1, 'pawn': 3, 'bishop': 1},
'w': {'king': 1, 'rook': 1, 'queen': 1}}
Received error:
Traceback (most recent call last):
File "/Users/Andrea_5K/Library/Mobile Documents/com~apple~CloudDocs/automateStuff2/ch5/flatToNest2.py", line 21, in <module>
count[k[0]][k[1:]] = v
KeyError: '1'
OP comment says output should be the count of each piece. That can be done as follows using setdefault
nested = {}
for k, v in board.items():
nested.setdefault(v[0], {}) # default dictionary for missing key
nested[v[0]][v[1:]] = nested[v[0]].get(v[1:], 0) + 1 # increment piece count
pprint.pprint(nested)
# Result
{'b': {'bishop': 1, 'king': 1, 'pawn': 3},
'w': {'king': 1, 'queen': 1, 'rook': 1}}
The problem in your code is that when you access nested[k[0]], you expect nested to already have this key, and you expect the corresponding value to be a dict.
The easiest way to solve this problem is to use a defaultdict(dict) that will create it on the fly when needed:
from collections import defaultdict
board = {
'1a': 'bking',
'4e': 'bpawn',
'2c': 'bpawn',
'3f': 'bpawn',
'5h': 'bbishop',
'6d': 'wking',
'7f': 'wrook',
'2b': 'wqueen'
}
nested = defaultdict(dict)
for k, v in board.items():
nested[k[0]][k[1:]] = v
print(nested)
# defaultdict(<class 'dict'>, {'1': {'a': 'bking'}, '4': {'e': 'bpawn'}, '2': {'c': 'bpawn', 'b': 'wqueen'}, '3': {'f': 'bpawn'}, '5': {'h': 'bbishop'}, '6': {'d': 'wking'}, '7': {'f': 'wrook'}})
If all you need is counts, use collections.Counter, and split the result using a collections.defaultdict afterward:
counts = defaultdict(dict)
for piece, count in Counter(board.values()).items():
counts[piece[0]][piece[1:]] = count
I have a function for filtering a list of dict based on the value of certain keys, like a SELECT * WHERE xxx query in SQL
list_of_dict = [
{'key1':val, 'key2':val},
{'key1':val, 'key2':val},
...
]
def filter_list(list_of_dict, key1, key2=None):
if key2:
filtered_list = [i for i in list_of_dict if i['key1']==key1 and i['key2']==key2]
else:
filtered_list = [i for i in list_of_dict if i['key1']==key1]
but when I have more keys as arguments to the function the if ...else... could go really long.
Is there a more pythonic way to do this?
If you have a large or variable number of keys, you can use all to loop over them. Here's an example where the key values are provided as keyword arguments:
def filter_dicts(dicts, **keys):
return [
d for d in dicts
if all(d[k] == v for k, v in keys.items())
]
As #juanpa.arrivillaga points out, dict_items objects behave like sets in many regards, so you can alternatively filter for dictionaries which have keys as a subset:
def filter_dicts(dicts, **keys):
return [d for d in dicts if keys.items() <= d.items()]
Example:
>>> dicts = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}]
>>> filter_dicts(dicts, x=1)
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}]
>>> filter_dicts(dicts, x=1, y=2)
[{'x': 1, 'y': 2}]
>>> filter_dicts(dicts, y=3)
[{'x': 1, 'y': 3}]
Is it possible to make a function that will return a nested dict depending on the arguments?
def foo(key):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
return d[key]
foo(['c']['d'])
I waiting for:
3
I'm getting:
TypeError: list indices must be integers or slices, not str
I understanding that it possible to return a whole dict, or hard code it to return a particular part of dict, like
if 'c' and 'd' in kwargs:
return d['c']['d']
elif 'c' and 'e' in kwargs:
return d['c']['e']
but it will be very inflexible
When you give ['c']['d'], you slice the list ['c'] using the letter d, which isin't possible. So what you can do is, correct the slicing:
foo('c')['d']
Or you could alter your function to slice it:
def foo(*args):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
d_old = dict(d) # if case you have to store the dict for other operations in the fucntion
for i in args:
d = d[i]
return d
>>> foo('c','d')
3
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
def funt(keys):
val = d
for key in keys:
if val:
val = val.get(key)
return val
funt(['c', 'd'])
Additionally to handle key not present state.
One possible solution would be to iterate over multiple keys -
def foo(keys, d=None):
if d is None:
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
if len(keys) == 1:
return d[keys[0]]
return foo(keys[1:], d[keys[0]])
foo(['c', 'd'])
I have a dictionary composed of {key: value}.
I select a set of keys from this dictionary.
I'd like to build a new dictionary with {keyA: set of all keys wich have the same value as keyA}.
I already have a solution: Is there a faster way to do it?
It seems very slow to me, and I imagine I'm not the only one in this case!
for key1 in selectedkeys:
if key1 not in seen:
seen.add(key1)
equal[key1] = set([key1])#egual to itself
for key2 in selectedkeys:
if key2 not in seen and dico[key1] == dico[key2]:
equal[key1].add(key2)
seen.update(equal[key1])
Try this
>>> a = {1:1, 2:1, 3:2, 4:2}
>>> ret_val = {}
>>> for k, v in a.iteritems():
... ret_val.setdefault(v, []).append(k)
...
>>> ret_val
{1: [1, 2], 2: [3, 4]}
def convert(d):
result = {}
for k, v in d.items(): # or d.iteritems() if using python 2
if v not in result:
result[v] = set()
result[v].add(k)
return result
or just use collections.defaultdict(set) if you are careful enough not to access any non key later :-)
So you want to create a dictionary that maps key to "the set of all keys which have the same value as key" for each selected key in a given source dictionary.
Thus, if the source dictionary is:
{'a': 1, 'b': 2, 'c': 1, 'd': 2, 'e': 3, 'f': 1, 'g': 3)
and the selected keys are a, b, and e, the result should be:
{'a': {'a', 'c', 'f'}, 'e': {'g', 'e'}, 'b': {'b', 'd'}}
One way to achieve this would be to use a defaultdict to build a value to key table, and then use that to build the required result from the specified keys:
from collections import defaultdict
def value_map(source, keys):
table = defaultdict(set)
for key, value in source.items():
table[value].add(key)
return {key: table[source[key]] for key in keys}
source = {'a': 1, 'b': 2, 'c': 1, 'd': 2, 'e': 3, 'f': 1, 'g': 3)
print(value_map(source, ['a', 'b', 'e']))
Output:
{'a': {'a', 'c', 'f'}, 'e': {'g', 'e'}, 'b': {'b', 'd'}}
Since you select a set of keys from the original dictionary. We can modify #Nilesh solution for your purpose.
a = {1:1, 2:1, 3:2, 4:2}
keys = [1, 3] # lets say this is the list of keys
ret_val = {}
for i in keys:
for k,v in a.items():
if a[i]==v:
ret_val.setdefault(i, []).append(k)
print (ret_val)
{1: [1, 2], 3: [3, 4]}
This was sort of stated in the comments by #Patrick Haugh:
d=your dictionary
s=set(d.values())
d2={i:[] for i in s}
for k in d:
d2[d[k]].append(k)
New to python here:
I'm trying to create a new list where every dict from initial list has an element removed, it exists:
arraylist = [{"x":1, "y":2}, {"x":3, "y":2}, {"x":5, "y":2}, {"x":33, "y":2}, {"x":1, "y":8}]
arraylist = map(lambda d: del d["y"] if "y" in d, arraylist)
I know I can do it using for, using del. But I'm looking to learn something new.
Use a list comprehension:
In [26]: [{x:d[x] for x in d if x != 'y'} for d in arraylist]
Out[26]: [{'x': 1}, {'x': 3}, {'x': 5}, {'x': 33}, {'x': 1}]
You can use filter like this
arraylist = [{"x":1, "y":2}, {"x":3, "y":2}, {"x":5, "y":2}, {"x":33, "y":2}, {"x":1, "y":8}]
arraylist = map(lambda d: dict(filter(lambda (k,v): k != "y", d.iteritems())), arraylist)
You can't use del in a lambda function because del is a statement and a lambda's body can only be a single expression. You can't put a statement inside an expression. You could make it work with an ordinary function:
def delete_y(d):
if "y" in d:
del d['y']
return d
Note that using del like this modifies the dictionary d in place. This means that returning the modified dictionary (and using the return value to build a new list) is sort of redundant. The original data structure the dictionary came from will already have the modified version.
Maybe its not the shortest way, but it is definitely a convenient way to remove
items from a list:
arraylist = [{"x":1, "y":2}, {"x":3, "y":2}, {"x":5, "y":2}, {"x":33, "y":2}, {"x":1, "y":8}]
print arraylist
def containsY(d):
if 'y' in d.keys():
del d['y']
return True
return False
filter(containsY, arraylist)
print arraylist
output:
[{'y': 2, 'x': 1}, {'y': 2, 'x': 3}, {'y': 2, 'x': 5}, {'y': 2, 'x': 33}, {'y': 8, 'x': 1}]
[{'x': 1}, {'x': 3}, {'x': 5}, {'x': 33}, {'x': 1}]