Using reduce on a list of dictionaries of dictionaries - python

Here is the given list.
Pets = [{'f1': {'dogs': 2, 'cats': 3, 'fish': 1},
'f2': {'dogs': 3, 'cats': 2}},
{'f1': {'dogs': 5, 'cats': 2, 'fish': 3}}]
I need to use the map and reduce function so that I can have a final result of
{'dogs': 10, 'cats': 7, 'fish': 4}
I have written a function using map
def addDict(d):
d2 = {}
for outKey, inKey in d.items():
for inVal in inKey:
if inVal in d2:
d2[inVal] += inKey[inVal]
else:
d2[inVal] = inKey[inVal]
return d2
def addDictN(L):
d2 = list(map(addDict, L))
print(d2)
That returns
[{'dogs': 5, 'cats': 5, 'fish': 1}, {'dogs': 5, 'cats': 2, 'fish': 3}]
It combines the f1 and f2 of the first and second dictionaries, but I am unsure of how to use reduce on the dictionaries to get the final result.

You can use collections.Counter to sum your list of counter dictionaries.
Moreover, your dictionary flattening logic can be optimised via itertools.chain.
from itertools import chain
from collections import Counter
Pets = [{'f1': {'dogs': 2, 'cats': 3, 'fish': 1},
'f2': {'dogs': 3, 'cats': 2}},
{'f1': {'dogs': 5, 'cats': 2, 'fish': 3}}]
lst = list(chain.from_iterable([i.values() for i in Pets]))
lst_sum = sum(map(Counter, lst), Counter())
# Counter({'cats': 7, 'dogs': 10, 'fish': 4})
This works for an arbitrary length list of dictionaries, with no key matching requirements across dictionaries.
The second parameter of sum is a start value. It is set to an empty Counter object to avoid TypeError.

Without using map and reduce, I would be inclined to do something like this:
from collections import defaultdict
result = defaultdict()
for fdict in pets:
for f in fdict.keys():
for pet, count in fdict[f].items():
result[pet] += count
Using reduce (which really is not the right function for the job, and is not in Python 3) on your current progress would be something like this:
from collections import Counter
pets = [{'dogs': 5, 'cats': 5, 'fish': 1}, {'dogs': 5, 'cats': 2, 'fish': 3}]
result = reduce(lambda x, y: x + Counter(y), pets, Counter())

You can use purely map and reduce like so:
Pets = [{'f1': {'dogs': 2, 'cats': 3, 'fish': 1},
'f2': {'dogs': 3, 'cats': 2}},
{'f1': {'dogs': 5, 'cats': 2, 'fish': 3}}]
new_pets = reduce(lambda x, y:[b.items() for _, b in x.items()]+[b.items() for _, b in y.items()], Pets)
final_pets = dict(reduce(lambda x, y:map(lambda c:(c, dict(x).get(c, 0)+dict(y).get(c, 0)), ['dogs', 'cats', 'fish']), new_pets))
Output:
{'fish': 4, 'cats': 7, 'dogs': 10}

Related

python iterate to append to a list, but all elements was changed to the last element

my_list = []
pre_dict = {}
for i in range(5):
my_dict = {'Ali': 2, 'Luna': 6}
if pre_dict:
pre_dict['Ali'] = i
print("[WTF] pre_dict: ", pre_dict)
my_list.append(pre_dict)
print("[WTF] my_list 1: ", my_list)
else:
my_dict['Ali'] = i
print("[WTF] my_dict: ", my_dict)
my_list.append(my_dict)
print("[WTF] my_list 2: ", my_list)
pre_dict = my_dict
print("[WTF] my_list x: ", my_list)
exit()
the output is :
[{'Ali': 4, 'Luna': 6}, {'Ali': 4, 'Luna': 6}, {'Ali': 4, 'Luna': 6}, {'Ali': 4, 'Luna': 6}, {'Ali': 4, 'Luna': 6}]
what I want is:
[{'Ali': 4, 'Luna': 6}, {'Ali': 3, 'Luna': 6}, {'Ali': 2, 'Luna': 6}, {'Ali': 1, 'Luna': 6}, {'Ali': 0, 'Luna': 6}]
I am very confused what is the logic. How to modify my code?
anyone give some advice? thanks !
You are facing a Python assignment problem. See: https://docs.python.org/3/library/copy.html
Assignment statements in Python do not copy objects, they create bindings between a target and an object.
Therefore, you should copy objects before append them to a list.
Example without copy.copy():
my_list = []
my_dict = {'A': None, 'B':6}
for i in range(5):
my_dict['A'] = i
my_list.append(my_dict)
print(my_list)
> [{'A': 4, 'B': 6}, {'A': 4, 'B': 6}, {'A': 4, 'B': 6}, {'A': 4, 'B': 6}, {'A': 4, 'B': 6}]
Example with copy.copy():
import copy
my_list = []
my_dict = {'A': None, 'B':6}
for i in range(5):
my_dict['A'] = i
my_list.append(copy.copy(my_dict))
print(my_list)
> [{'A': 0, 'B': 6}, {'A': 1, 'B': 6}, {'A': 2, 'B': 6}, {'A': 3, 'B': 6}, {'A': 4, 'B': 6}]
If you want a reverse order, use range(5-1, -1, -1).
You can do it with a list comprehension:
[{'Ali':i, 'Luna':6} for i in reversed(range(5))]
Output:
[{'Ali': 4, 'Luna': 6},
{'Ali': 3, 'Luna': 6},
{'Ali': 2, 'Luna': 6},
{'Ali': 1, 'Luna': 6},
{'Ali': 0, 'Luna': 6}]
You can use itemgetter from operator library to sort the list of dics, and I fixed your code to get the right values.
from operator import itemgetter
my_list = []
pre_dict = {}
for i in range(5):
my_dict = {'Ali': 0, 'Luna': 6}
if pre_dict:
pre_dict['Ali'] = i
print("[WTF] pre_dict: ", pre_dict)
my_list.append(pre_dict)
print("[WTF] my_list 1: ", my_list)
else:
my_dict['Ali'] = i
print("[WTF] my_dict: ", my_dict)
my_list.append(my_dict)
print("[WTF] my_list 2: ", my_list)
Sortedlist = sorted(my_list, key=itemgetter('Ali'), reverse=True)
print("[WTF] my_list x: ", Sortedlist)

How to make all keys except first key lowercase in dictionary

Here is a dictionary
dict1 = {'math': {'JOHN': 7,
'LISA': 4,
'KARYN': 3},
'eng': {'LISA': 5,
'TOBY':4,
'KARYN':11,
'RYAN':3},
'phy': {'KARYN': 7,
'JOHN': 7,
'STEVE':9,
'JOE':9}}
I would like to make the all letters in the keys except the 1st lower case.
This is what i've attempted
for i in dict1:
dict1 = dict(k.lower(), v) for k =! k[0], v in dict1[i].items())
dict1
It's failing because i'm not exactly sure how to apply the condition so that only the 1st letter remains capital.
If I understand correctly:
>>> {k: {kk.capitalize(): vv for kk, vv in v.items()} for k, v in dict1.items()}
{'math': {'John': 7, 'Lisa': 4, 'Karyn': 3},
'eng': {'Lisa': 5, 'Toby': 4, 'Karyn': 11, 'Ryan': 3},
'phy': {'Karyn': 7, 'John': 7, 'Steve': 9, 'Joe': 9}}
You can just create a new dictionary using the new keys and delete the old one.
from collections import defaultdict
# this creates a dictionary of dictionaries
dict2 = defaultdict(dict)
for key in dict1.keys():
for name in dict1[key]:
# get only the first letter in caps and the rest in lower
newname = name[0] + name.lower()[1:]
# create a new entry in the new dictionray using the old one
dict2[key][newname] = dict1[key][name]
The output is:
defaultdict(dict,
{'eng': {'Karyn': 11, 'Lisa': 5, 'Ryan': 3, 'Toby': 4},
'math': {'John': 7, 'Karyn': 3, 'Lisa': 4},
'phy': {'Joe': 9, 'John': 7, 'Karyn': 7, 'Steve': 9}})
which can be assessed just like a regular dictionary.
In python there is a function called capitalize(). Maybe it could help?
your_string = "ABRAKADABRA!"
print(your_string.capitalize())
returns
Abrakadabra!
https://www.geeksforgeeks.org/string-capitalize-python/

Three lists zipped into list of dicts

Consider the following:
>>> # list of length n
>>> idx = ['a', 'b', 'c', 'd']
>>> # list of length n
>>> l_1 = [1, 2, 3, 4]
>>> # list of length n
>>> l_2 = [5, 6, 7, 8]
>>> # first key
>>> key_1 = 'mkt_o'
>>> # second key
>>> key_2 = 'mkt_c'
How do I zip this mess to look like this?
{
'a': {'mkt_o': 1, 'mkt_c': 5},
'b': {'mkt_o': 2, 'mkt_c': 6},
'c': {'mkt_o': 3, 'mkt_c': 6},
'd': {'mkt_o': 4, 'mkt_c': 7},
...
}
The closest I've got is something like this:
>>> dict(zip(idx, zip(l_1, l_2)))
{'a': (1, 5), 'b': (2, 6), 'c': (3, 7), 'd': (4, 8)}
Which of course has tuples as values instead of dictionaries, and
>>> dict(zip(('mkt_o', 'mkt_c'), (1,2)))
{'mkt_o': 1, 'mkt_c': 2}
Which seems like it might be promising, but again, fails to meet requirements.
{k : {key_1 : v1, key_2 : v2} for k,v1,v2 in zip(idx, l_1, l_2)}
Solution 1: You may use zip twice (actually thrice) with dictionary comprehension to achieve this as:
idx = ['a', 'b', 'c', 'd']
l_1 = [1, 2, 3, 4]
l_2 = [5, 6, 7, 8]
keys = ['mkt_o', 'mkt_c'] # yours keys in another list
new_dict = {k: dict(zip(keys, v)) for k, v in zip(idx, zip(l_1, l_2))}
Solution 2: You may also use zip with nested list comprehension as:
new_dict = dict(zip(idx, [{key_1: i, key_2: j} for i, j in zip(l_1, l_2)]))
Solution 3: using dictionary comprehension on top of zip as shared in DYZ's answer:
new_dict = {k : {key_1 : v1, key_2 : v2} for k,v1,v2 in zip(idx, l_1, l_2)}
All the above solutions will return new_dict as:
{
'a': {'mkt_o': 1, 'mkt_c': 5},
'b': {'mkt_o': 2, 'mkt_c': 6},
'c': {'mkt_o': 3, 'mkt_c': 7},
'd': {'mkt_o': 4, 'mkt_c': 8}
}
You're working with dicts, lists, indices, keys and would like to transpose the data. It might make sense to work with pandas (DataFrame, .T and .to_dict):
>>> import pandas as pd
>>> idx = ['a', 'b', 'c', 'd']
>>> l_1 = [1, 2, 3, 4]
>>> l_2 = [5, 6, 7, 8]
>>> key_1 = 'mkt_o'
>>> key_2 = 'mkt_c'
>>> pd.DataFrame([l_1, l_2], index=[key_1, key_2], columns = idx)
a b c d
mkt_o 1 2 3 4
mkt_c 5 6 7 8
>>> pd.DataFrame([l_1, l_2], index=[key_1, key_2], columns = idx).T
mkt_o mkt_c
a 1 5
b 2 6
c 3 7
d 4 8
>>> pd.DataFrame([l_1, l_2], index=[key_1, key_2], columns = idx).to_dict()
{'a': {'mkt_o': 1, 'mkt_c': 5},
'b': {'mkt_o': 2, 'mkt_c': 6},
'c': {'mkt_o': 3, 'mkt_c': 7},
'd': {'mkt_o': 4, 'mkt_c': 8}
}
It can also be done with dict, zip, map and repeat from itertools:
>>> from itertools import repeat
>>> dict(zip(idx, map(dict, zip(zip(repeat(key_1), l_1), zip(repeat(key_2), l_2)))))
{'a': {'mkt_c': 5, 'mkt_o': 1}, 'c': {'mkt_c': 7, 'mkt_o': 3}, 'b': {'mkt_c': 6, 'mkt_o': 2}, 'd': {'mkt_c': 8, 'mkt_o': 4}}

Selecting items in a dictionary using python

My goal is to first select the first 3 items in the dictionary below. I would also like to select items with values greater than 1.
dic=Counter({'school': 4, 'boy': 3, 'old': 3, 'the': 1})
My attempt:
1.>>> {x:x for x in dic if x[1]>1}
{'boy': 'boy', 'the': 'the', 'old': 'old', 'school': 'school'}
2.>>>dic[:3]
TypeError: unhashable type
Desired output: Counter({'school': 4, 'boy': 3, 'old': 3})
Thanks for your suggestions.
For items with count greater than one:
>>> [x for x in dic if dic[x] > 1]
['boy', 'school', 'old']
For the three most common items:
>>> [x for x, freq in dic.most_common(3)]
['school', 'boy', 'old']
To get dictionaries:
>>> {x: freq for x,freq in dic.items() if freq > 1}
{'boy': 3, 'school': 4, 'old': 3}
>>> {x: freq for x,freq in dic.most_common(3)}
{'boy': 3, 'school': 4, 'old': 3}
Note: Those are ordinary dictionaries. Use Counter(result) to turn them back into Counters. Alternatively to the dictionary comprehension you can also use the builtin dict function to turn a list of tuples into a dictionary, and then make a Counter from that.
>>> Counter(dict(dic.most_common(3)))
Counter({'school': 4, 'boy': 3, 'old': 3})

python sorting dictionary by length of values

I have found many threads for sorting by values like here but it doesn't seem to be working for me...
I have a dictionary of lists that have tuples. Each list has a different amount of tuples. I want to sort the dictionary by how many tuples each list contain.
>>>to_format
>>>{"one":[(1,3),(1,4)],"two":[(1,2),(1,2),(1,3)],"three":[(1,1)]}
>>>for key in some_sort(to_format):
print key,
>>>two one three
Is this possible?
>>> d = {"one": [(1,3),(1,4)], "two": [(1,2),(1,2),(1,3)], "three": [(1,1)]}
>>> for k in sorted(d, key=lambda k: len(d[k]), reverse=True):
print k,
two one three
Here is a universal solution that works on Python 2 & Python 3:
>>> print(' '.join(sorted(d, key=lambda k: len(d[k]), reverse=True)))
two one three
dict= {'a': [9,2,3,4,5], 'b': [1,2,3,4, 5, 6], 'c': [], 'd': [1,2,3,4], 'e': [1,2]}
dict_temp = {'a': 'hello', 'b': 'bye', 'c': '', 'd': 'aa', 'e': 'zz'}
def sort_by_values_len(dict):
dict_len= {key: len(value) for key, value in dict.items()}
import operator
sorted_key_list = sorted(dict_len.items(), key=operator.itemgetter(1), reverse=True)
sorted_dict = [{item[0]: dict[item [0]]} for item in sorted_key_list]
return sorted_dict
print (sort_by_values_len(dict))
output:
[{'b': [1, 2, 3, 4, 5, 6]}, {'a': [9, 2, 3, 4, 5]}, {'d': [1, 2, 3, 4]}, {'e': [1, 2]}, {'c': []}]

Categories