メモ: groupbyと、3つ目の引数が0のreduce関数

finalfusionさんにコメントで書いて頂いたコードを把握したときの備忘メモです。

for e in itertools.groupby(data_list, key=lambda z:z.code):
  print(e[0], functools.reduce(lambda a,b:a+b, [x.value for x in e[1]], 0))

(これをぱっと見で理解できるレベルにまだないわけです…)

ポイントは、

  • itertools.groupbyの戻り値
  • 初期値を持つreduce関数

の2つです。

itertools.groupbyの戻り値

groupby() は、下敷きになっているイテラブルから、連続して同じキー値を持つ要素を集めて、キー値とイテレータの 2-タプルを返していきます。

http://www.python.jp/doc/nightly/howto/functional.html

groupbyは、キー(e[0])と、要素へのイテレータ(e[1])を要素に持つタプルを返し続けます。

次のページでは、groupbyの結果について、

keyfunc(v) の値でグループ化したサブイテレータ

http://www.python.jp/doc/nightly/library/itertools.html

と書いてあり、サブ???という感じでしたが、上の記述で分かりました。

初期値を持つreduce関数

reduceの引数の0って何だろうと思ったら、チュートリアルに解説がありました。

3 つめの引数をわたして、初期値を指定することもできます。この場合、空のシーケンスを渡すと初期値が返されます。それ以外の場合には、まず初期値とシーケンス中の最初の要素に対して関数が適用され、次いでその結果とシーケンスの次の要素に対して適用され、以降これが繰り返されます。例えば以下のようになります。

http://www.python.jp/doc/release/tutorial/datastructures.html
def sum(seq):
    def add(x,y): return x+y
    return reduce(add, seq, 0)

sum(range(1, 11))
55
sum([])
0

lambdaを使って上のsum関数を書き換えると、こうなるでしょう。

def sum(seq):
    return reduce(lambda x,y:x+y, seq, 0)

おっ…。冒頭のコードに近い形になりました。

このreduceは、初期値0+seq[1]、(初期値0+seq[1])+seq[2]…という具合に、戻り値を作っていきます。

つまり、冒頭のコードは、ある値をキーにする集合(setの要素に該当するモノ)であるところのe[1]に対して、その処理を行っているわけですね。

なるほど。やっと理解できましたw ありがとうございます。