Как объединить словари, используя веса? - PullRequest
2 голосов
/ 15 июля 2011
d1 = {'weight':1, 'data': { 'apples': 8, 'oranges': 7 } }
d2 = {'weight':3, 'data': { 'apples': 4, 'bananas': 3 } }
all_dictionaries = [d1, d2, ... ]

def mergeDictionariesWithWeight(all_dictionaries)

Как объединить эти словари (если они совпадают, множественное значение с весом)

Функция вернет:

{ 'apples': 4, 'oranges': 7, 'bananas': 3 }

Яблоки 4, потому что 8 * .25 + 4 * .75

Редактировать: Я только что написал один, который берет среднее, что-то вроде этого. Но, конечно, это действительно отличается от того, что я хочу сделать, потому что я помещаю все в список и просто делю на длину.

result = {}
keymap = {}
for the_dict in dlist:
    for (k, v) in the_dict.items():
        if not keymap.has_key(k):
            keymap[k] = []
        keymap[k].append(v)
for (k, v) in keymap.items():
    average = sum(int(x) for x in keymap[k]) / float(len(keymap[k]))
    result[k] = float(average)
return result

Ответы [ 5 ]

7 голосов
/ 15 июля 2011
>>> from collections import defaultdict
>>> d=defaultdict(lambda:(0,0))
>>> for D in all_dictionaries:
...   weight = D['weight']
...   for k,v in D['data'].items():
...     d[k]=d[k][0]+weight*v,d[k][1]+weight
... 
>>> dict((k,v[0]/v[1]) for k,v in d.items())
{'apples': 5, 'oranges': 7, 'bananas': 3}

Если вам нужен результат с плавающей точкой

>>> dict((k,1.*v[0]/v[1]) for k,v in d.items())
{'apples': 5.0, 'oranges': 7.0, 'bananas': 3.0}

Примечания о defaultdict

Часто вы видите defaultdict(int) или defaultdict(list) может быть, даже defaultdict(set).Аргумент defaultdict должен вызываться без параметров.Результат вызова этого параметра используется всякий раз, когда обнаруживается, что ключ отсутствует.то есть - вызов этого возвращает значение по умолчанию для словаря

, например

>>> d=defaultdict(int)
>>> d[1]
0
>>> d['foo']
0

Это часто используется для подсчета вещей, потому что int() возвращает 0. Есливы хотите, чтобы значение по умолчанию было 1 вместо 0, это более сложно, потому что вы не можете передать параметр в int, но все, что вам нужно, это вызываемый элемент, который возвращает 1. Это можно сделать без особых хлопот, используя лямбда функция.

>>> d=defaultdict(lambda:1)
>>> d[1]
1
>>> d['foo']
1

В этом ответе я хочу отслеживать взвешенную сумму и общую массу.Я могу сделать это, используя 2-кортеж в качестве значения по умолчанию.

>>> d=defaultdict(lambda:(0,0))
>>> d[1]
(0, 0)
>>> d['foo']
(0, 0)
1 голос
/ 15 июля 2011
from collections import defaultdict

def merge_dictionaries_with_weight(all_dictionaries):
    totals = defaultdict(int)
    result = defaultdict(int)
    for each in all_dictionaries:
        weight = float(each['weight'])
        for key, value in each['data'].items():
            totals[key] += weight
            result[key] += weight * value
    for key, total in totals.items():
       result[key] /= total
    return result
1 голос
/ 15 июля 2011

попробуйте это:

def mergeDictionariesWithWeight(all_dictionaries):
    weightSum = 0
    weightDictionary ={}    

    for dictionary in all_dictionaries: 

        weight = dictionary['weight']
        data = dictionary['data']

        #find the total weight of the elements in data
        for (k,v) in data.items(): 
            if k in weightDictionary:
                weightDictionary[k] += weight*v
        weightSum += weight 
        #normalize the results by deviding by the weight sum
        for (key, value) in weightDictionary:
            weightDictionary[key] = value / float(weightSum)
    return weightDictionary 

d1 = {'weight':1, 'data': { 'apples': 8, 'oranges': 7 } }
d2 = {'weight':3, 'data': { 'apples': 4, 'bananas': 3 } }
all_dictionaries = [d1, d2]

mergeDictionariesWithWeight(all_dictionaries)



1 голос
/ 15 июля 2011

Вот решение, которое сначала использует, собирает элементы в список с использованием временного dict, а затем вычисляет окончательный взвешенный dict. Вероятно, это можно сделать без временного, но это легко понять.

from collections import defaultdict

def mergeDictionariesWithWeight(dlist):
    tmp = defaultdict(list)
    for d in dlist:
        weight = d['weight']
        for k, v in d['data'].items():
            tmp[k].append((weight, v))
    r = {}
    for k, v in tmp.items():
        # If there's just one item, ignore the weight
        if len(v) == 1:
            r[k] = v[0][1]
        else:
            total_weight = sum((x[0] for x in v), 0.0)
            r[k] = sum(x[1] * x[0]/total_weight for x in v)
    return r

Возвращает: {'яблоки': 5,0, 'апельсины': 7, 'бананы': 3} (потому что 8 * .25 + 4 * .75 = 5,0)

0 голосов
/ 15 июля 2011

Алгоритмически неотличим от gnibbler , но каким-то образом выражение генератора мне нравится.

>>> from collections import defaultdict
>>> weights, values = defaultdict(int), defaultdict(int)
>>> key_weight_value = ((key, d['weight'], value) 
                        for d in all_dictionaries 
                        for key, value in d['data'].iteritems())
>>> for k, w, v in key_weight_value:
...     weights[k], values[k] = weights[k] + w, values[k] + w * v
... 

>>> dict((k, values[k] * 1.0 / weights[k]) for k in weights)
{'apples': 5.0, 'oranges': 7.0, 'bananas': 3.0}
...