Compare for min values in nested dict - python

I have a list of pairwise dictionary that goes like this:
[{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Ben': {'star': 5, 'banana': 12, 'melon': 1}},
{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Cam': {'star': 65, 'melon': 1}},
{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Den': {'juice': 0, 'cake': 4}}, ...]
I need to compare the pairs for min value(in fraction) but we only focus on the items in focal person, in this case Anna.
Take the first pair for example,
the items that 'Anna' and 'Ben' have in common are 'star' and 'banana'. Since we only care about the focal person 'Anna', we just need to find the min of 'star', 'banana', and 'bag'.
Then, subtract with 1 after comparing the pair for min values:
Ans = 1 - min('star':[5/24, 5/18], 'banana':[12/24, 12/24], 'bag':[7/24, 0])
So the ideal result will be
Anna-Ben = Ans1
Anna-Cam = Ans2
Anna-Den = Ans3
.
.
.
.
Any idea how to accomplish this? Thank you so much and sorry for my english!
*Edit:
Hi, thanks for your reply, but the thing I want is 1 minus the min of each item. Like in the 'Anna-Ben' pair,
min of 'star' between [5/24, 5/18] is 5/24,
min of 'banana' between [12/24, 12/18] is 12/24, and
min of 'bag' between [7/24, 0] is 0 (only Anna has bag, Ben doesn't has bag so it's zero).
And we ignore the 'melon' item in 'Ben' because we only concern the focal person 'Anna'.
So the final result should be [1 - 5/24 - 12/24 - 0 = 7/24] for the 'Anna-Ben' pair.

I hope I understood your problem correctly.
data = [
{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Ben': {'star': 5, 'banana': 12, 'melon': 1}},
{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Cam': {'star': 65, 'melon': 1}},
{'Anna': {'star': 5, 'banana': 12, 'bag': 7}, 'Den': {'juice': 0, 'cake': 4}}
]
results = {}
# iterate over each pair
for pair in data:
anna_data = pair.pop("Anna")
other_name, other_data = pair.popitem() # get comparing data
result = 1
anna_sum = float(sum(anna_data.values()))
other_sum = float(sum(other_data.values()))
# iterate over each of anna's item
for item, anna_val in anna_data.items():
other_val = other_data.get(item, 0) # set 0 if the item is not found in other_data
min_item = min(anna_val/anna_sum, other_val/other_sum)
result -= min_item
# save the result to a wonderful dict
key = "Anna-%s" % other_name
results[key] = result
print(results)
Result:
{'Anna-Ben': 0.29166666666666663, 'Anna-Cam': 0.7916666666666666,'Anna-Den': 1.0}
By the way I destroyed the data list, if you want to keep it intact make a copy() of it before computing this.

Related

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/

Using reduce on a list of dictionaries of dictionaries

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}

Ordering a Django queryset based on other list with ids and scores

I'm a bit mentally stuck at something, that seems really simple at first glance.
I'm grabbing a list of ids to be selected and scores to sort them based on.
My current solution is the following:
ids = [1, 2, 3, 4, 5]
items = Item.objects.filter(pk__in=ids)
Now I need to add a score based ordering somehow so I'll build the following list:
scores = [
{'id': 1, 'score': 15},
{'id': 2, 'score': 7},
{'id': 3, 'score': 17},
{'id': 4, 'score': 11},
{'id': 5, 'score': 9},
]
ids = [score['id'] for score in scores]
items = Item.objects.filter(pk__in=ids)
So far so good - but how do I actually add the scores as some sort of aggregate and sort the queryset based on them?
Sort the scores list, and fetch the queryset using in_bulk().
scores = [
{'id': 1, 'score': 15},
{'id': 2, 'score': 7},
{'id': 3, 'score': 17},
{'id': 4, 'score': 11},
{'id': 5, 'score': 9},
]
sorted_scores = sorted(scores) # use reverse=True for descending order
ids = [score['id'] for score in scores]
items = Item.objects.in_bulk(ids)
Then generate a list of the items in the order you want:
items_in_order = [items[x] for x in ids]

Summing over an array and then multiply by a dictionary [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
Fruits = ['apple', 'orange', 'banana', 'kiwi']
A = [4, 3, 10, 8]
B = {'apple': {'Bill': 4, 'Jan': 3, 'Frank': 5},
'orange': {'Bill': 0, 'Jan': 1, 'Frank': 5},
'banana': {'Bill': 8, 'Jan': 6, 'Frank': 2},
'kiwi': {'Bill': 4, 'Jan': 2, 'Frank': 7}}
I am trying to sum over all the fruits of A and multiply that by B. I am having trouble doing this A is an array of just numbers and B is a dictionary. This is where I am getting confused. I am a new Python user. The numbers in A are in the same position relative to Fruits (the first number in A is the number of apples). Would this involve using sum(A)?
Sorry folks for the lack of details. Here is some clarity. I have fruits and I have numbers of fruits that each person has based on the type. I am wanting to sum all of the values of each fruit type in B such that I get:
apple = 12
orange = 6
banana = 16
kiwi = 13
Now, I want to multiple these numbers, by A, but keeping in mind that the first number in A, is apple, then orange, and so on to get a new array:
Solution = [48,18,160,104] #solution order is apple, orange, banana, kiwi
Assuming that you want to multply the sum of the fruits for each person (in B) by the cost in A, you can do the following list comprehension:
>>> [cost * sum(B[fruit].values()) for cost, fruit in zip(A, Fruits)]
[48, 18, 160, 104]
fruit_costs = {fruit_name:fruit_cost for fruit_name,fruit_cost in zip(Fruits,A)
for fruit in Fruits:
print "Fruit:",fruit,"=",sum(B[fruit].values())*fruit_costs[fruit]
I guess?
Merge everything into one big dictionary; everything here is just properties of fruits:
>>> for i, fruit in enumerate(fruits):
>>> B[fruit]['cost'] = A[i]
>>> B
{'banana': {'Frank': 2, 'Jan': 6, 'Bill': 8, 'cost': 10}, 'apple': {'Frank': 5, 'Jan': 3, 'Bill': 4, 'cost': 4}, 'orange': {'Frank': 5, 'Jan': 1, 'Bill': 0, 'cost': 3}, 'kiwi': {'Frank': 7, 'Jan': 2, 'Bill': 4, 'cost': 8}}
Rename "B" to "fruits" (losing the old value of "fruits"):
>>> fruits = B
Calculate fruit cost for each fruit:
>>> for fruitname in fruits:
... fruit = test.B[fruitname]
... fruit['total'] = fruit['Frank'] + fruit['Bill'] + fruit['Jan']
... fruit['total cost'] = fruit['cost'] * fruit['total']
...
>>> fruits
{'banana': {'total': 16, 'Frank': 2, 'Jan': 6, 'total cost': 160, 'Bill': 8, 'cost': 10}, 'apple': {'total': 12, 'Frank': 5, 'Jan': 3, 'total cost': 48, 'Bill': 4, 'cost': 4}, 'orange': {'total': 6, 'Frank': 5, 'Jan': 1, 'total cost': 18, 'Bill': 0, 'cost': 3}, 'kiwi': {'total': 13, 'Frank': 7, 'Jan': 2, 'total cost': 104, 'Bill': 4, 'cost': 8}}
Calculate total cost:
>>> total = sum(fruits[fruit]['total cost'] for fruit in fruits)
Or if that last line is awkward since you're new to Python, you can expand it out into:
>>> total = 0
>>> for fruitname in fruits:
... fruit = fruits[fruitname]
... total += fruit['total cost']
...
Either way:
>>> total
330

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})

Categories