37

複数のサーバーからのログをマージしようとしています。各ログはタプル ( datecount) のリストです。date複数回表示される可能性があり、結果の辞書にすべてのサーバーからのすべてのカウントの合計を保持する必要があります。

たとえば、いくつかのデータを使用した私の試みは次のとおりです。

from collections import defaultdict

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input=[a,b,c]

output=defaultdict(int)
for d in input:
        for item in d:
           output[item[0]]+=item[1]
print dict(output)

これにより、次のことが得られます。

{'14.5': 100, '16.5': 100, '13.5': 100, '15.5': 200}

予想通り。

コードを見た同僚のために、私はバナナを食べようとしています。彼女は、これらのネストされた for ループを使わずに、より Pythonic でエレガントな方法が必要であると主張しています。何か案は?

4

4 に答える 4

41

これ以上簡単なことはないと思います:

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input=[a,b,c]

from collections import Counter

print sum(
    (Counter(dict(x)) for x in input),
    Counter())

(マルチセットとも呼ばれますCounter)は、データの最も自然なデータ構造です(要素が複数回属することができるセットのタイプ、または同等のもの - セマンティクス Element -> OccurrenceCount を持つマップ)。タプルのリストではなく、そもそも。


また可能:

from collections import Counter
from operator import add

print reduce(add, (Counter(dict(x)) for x in input))

reduce(add, seq)の代わりに使用するsum(seq, initialValue)方が一般的に柔軟性が高く、冗長な初期値の受け渡しをスキップできます。

operator.and_合計の代わりにマルチセットの交点を見つけるためにも使用できることに注意してください。


上記のバリアントは、すべてのステップで新しいカウンターが作成されるため、非常に遅くなります。それを修正しましょう。

マージされたデータを含むCounter+Counternew を返すことがわかっています。Counterこれは問題ありませんが、余分な作成は避けたいと思います。Counter.update代わりに使用しましょう:

update(self, iterable=None, **kwds) unbound collections.Counter メソッド

dict.update() と似ていますが、カウントを置き換える代わりに追加します。ソースは、イテラブル、辞書、または別の Counter インスタンスにすることができます。

それが私たちが望むものです。reduce互換性のある関数でラップして、何が起こるか見てみましょう。

def updateInPlace(a,b):
    a.update(b)
    return a

print reduce(updateInPlace, (Counter(dict(x)) for x in input))

これは、OPのソリューションよりもわずかに遅いだけです。

ベンチマーク: http://ideone.com/7IzSx ( astynaxのおかげで、さらに別のソリューションで更新されました)

(また: どうしてもワンライナーが必要な場合はupdateInPlacelambda x,y: x.update(y) or xwhich を置き換えることができます。

于 2012-07-02T08:50:23.657 に答える
10
from collections import Counter


a = [("13.5",100)]
b = [("14.5",100), ("15.5", 100)]
c = [("15.5",100), ("16.5", 100)]

inp = [dict(x) for x in (a,b,c)]
count = Counter()
for y in inp:
  count += Counter(y)
print(count)

出力:

Counter({'15.5': 200, '14.5': 100, '16.5': 100, '13.5': 100})

編集:ダンカンが提案した ように、これらの 3 行を 1 行に置き換えることができます。

   count = Counter()
    for y in inp:
      count += Counter(y)

で置き換えます:count = sum((Counter(y) for y in inp), Counter())

于 2012-07-02T08:41:22.627 に答える
7

itertools のgroupbyを使用できます:

from itertools import groupby, chain

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input = sorted(chain(a,b,c), key=lambda x: x[0])

output = {}
for k, g in groupby(input, key=lambda x: x[0]):
  output[k] = sum(x[1] for x in g)

print output

groupby2 つのループと aの代わりに を使用するとdefaultdict、コードがより明確になります。

于 2012-07-02T08:35:34.200 に答える
1

Counter または defaultdict を使用するか、私のバリアントを試すことができます。

def merge_with(d1, d2, fn=lambda x, y: x + y):
    res = d1.copy() # "= dict(d1)" for lists of tuples
    for key, val in d2.iteritems(): # ".. in d2" for lists of tuples
        try:
            res[key] = fn(res[key], val)
        except KeyError:
            res[key] = val
    return res

>>> merge_with({'a':1, 'b':2}, {'a':3, 'c':4})
{'a': 4, 'c': 4, 'b': 2}

またはさらに一般的な:

def make_merger(fappend=lambda x, y: x + y, fempty=lambda x: x):
    def inner(*dicts):
        res = dict((k, fempty(v)) for k, v
            in dicts[0].iteritems()) # ".. in dicts[0]" for lists of tuples
        for dic in dicts[1:]:
            for key, val in dic.iteritems(): # ".. in dic" for lists of tuples
                try:
                    res[key] = fappend(res[key], val)
                except KeyError:
                    res[key] = fempty(val)
        return res
    return inner

>>> make_merger()({'a':1, 'b':2}, {'a':3, 'c':4})
{'a': 4, 'c': 4, 'b': 2}

>>> appender = make_merger(lambda x, y: x + [y], lambda x: [x])
>>> appender({'a':1, 'b':2}, {'a':3, 'c':4}, {'b':'BBB', 'c':'CCC'})
{'a': [1, 3], 'c': [4, 'CCC'], 'b': [2, 'BBB']}

また、メソッドをサブクラス化しdictて実装することもできます。__add__

于 2012-07-02T09:40:02.043 に答える