web-dev-qa-db-ja.com

累計のリスト内包

数字のリストから現在の合計を取得したい。

デモの目的で、rangeを使用して番号の連続リストから始めます

_a = range(20)

runningTotal = []
for n in range(len(a)):
    new = runningTotal[n-1] + a[n] if n > 0 else a[n]
    runningTotal.append(new)

# This one is a syntax error
# runningTotal = [a[n] for n in range(len(a)) if n == 0 else runningTotal[n-1] + a[n]]

for i in Zip(a, runningTotal):
    print "{0:>3}{1:>5}".format(*i)
_

収量

_  0    0
  1    1
  2    3
  3    6
  4   10
  5   15
  6   21
  7   28
  8   36
  9   45
 10   55
 11   66
 12   78
 13   91
 14  105
 15  120
 16  136
 17  153
 18  171
 19  190
_

ご覧のとおり、ループの反復ごとに空のリスト_[]_を初期化し、次にappend()を初期化します。リスト内包のような、これに対するよりエレガントな方法はありますか?

24
Kit

リスト内包表記には、作成しているリストそのものを参照するための適切な(クリーンでポータブルな)方法がありません。優れたエレガントなアプローチの1つは、ジェネレーターで作業を行うことです。

_def running_sum(a):
  tot = 0
  for item in a:
    tot += item
    yield tot
_

もちろん、これをリストとして取得するには、list(running_sum(a))を使用します。

28
Alex Martelli

numpy を使用できる場合は、これを行うcumsumという名前の組み込み関数があります。

import numpy
tot = numpy.cumsum(a)  # returns a numpy.ndarray
tot = list(tot)        # if you prefer a list
26
Mr Fooz

これはPythonでは2行で実装できます。

デフォルトのパラメーターを使用すると、外部でaux変数を維持する必要がなくなり、リストに対してmapを実行するだけです。

def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))
10
satoru

'elegant'についてはよくわかりませんが、次の方がはるかに単純で直感的だと思います(追加の変数が必要です)。

a = range(20)

runningTotal = []

total = 0
for n in a:
  total += n
  runningTotal.append(total)

同じことを行うための機能的な方法は次のとおりです。

a = range(20)
runningTotal = reduce(lambda x, y: x+[x[-1]+y], a, [0])[1:]

...しかし、それははるかに読みにくく、保守しにくいなどです。

@Omnifarousは、これを次のように改善する必要があることを示唆しています。

a = range(20)
runningTotal = reduce(lambda l, v: (l.append(l[-1] + v) or l), a, [0])

...しかし、私はまだそれが私の最初の提案よりもすぐには理解できないと感じています。

Kernighanの言葉を思い出してください。「デバッグは、最初にコードを書くよりも2倍難しいです。したがって、コードをできるだけ巧妙に書くと、定義上、デバッグするのに十分賢くなりません。」

8
pjz

itertools.accumulate() を使用します。次に例を示します。

from itertools import accumulate

a = range(20)
runningTotals = list(accumulate(a))

for i in Zip(a, runningTotals):
    print "{0:>3}{1:>5}".format(*i)

これはPython 3. Python 2では、 more-itertools パッケージのバックポートを使用できます。

7
mleyfman

Bisect_leftを使用できる累積度数を生成するために同じことをしたかったのですが、これがリストの生成方法です。

[ sum( a[:x] ) for x in range( 1, len(a)+1 ) ]
4
user1908017

線形時間と空間でのもう1つのワンライナー。

def runningSum(a):
    return reduce(lambda l, x: l.append(l[-1]+x) or l if l else [x], a, None)

私はここで線形空間を強調しています。なぜなら、他の提案された回答で見たワンライナーのほとんどは、パターンlist + [sum]に基づくものまたはchainイテレーターを使用するもの---生成するO(n)リストまたはジェネレーターは、ガベージコレクターに大きなストレスを与えるため、これと比較してパフォーマンスが非常に低くなります。

2
nickie

線形時間解のワンライナーは次のとおりです。

list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])

例:

l = range(10)
list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])
>>> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

要するに、reduceは、合計を累積してリストを作成するリストを超えます。最終 x[0]はリストを返します。x[1]は現在の合計値になります。

2
topkara

これにはコルーチンを使用します:

def runningTotal():
    accum = 0
    yield None
    while True:
        accum += yield accum

tot = runningTotal()
next(tot)
running_total = [tot.send(i) for i in xrange(N)]
1
aaronasterling

Python 3.8を開始し、 代入式(PEP 572):=演算子)を導入すると、リスト内包内で変数を使用およびインクリメントできます。

# items = range(7)
total = 0
[(x, total := total + x) for x in items]
# [(0, 0), (1, 1), (2, 3), (3, 6), (4, 10), (5, 15), (6, 21)]

この:

  • 変数total0に初期化します。これは、現在の合計を表します。
  • 各アイテムについて、これは両方:
    • 代入式を介して、現在のループ項目(total := total + x)でtotalをインクリメントします
    • 同時に、生成されたマップされたタプルの一部としてtotalの新しい値を返します
0
Xavier Guihot

あなたは2つのことを探しています:fold(reduce)と、私が実行と呼んでいる別の関数の結果のリストを保持する面白い関数です。初期パラメータのあるバージョンとないバージョンの両方を作成しました。いずれにせよ、これらは最初の[]で減らす必要があります。

def last_or_default(list, default):
    if len(list) > 0:
        return list[-1]
    return default

def initial_or_apply(list, f, y):
    if list == []:
        return [y]
    return list + [f(list[-1], y)]

def running_initial(f, initial):
    return (lambda x, y: x + [f(last_or_default(x,initial), y)])

def running(f):
    return (lambda x, y: initial_or_apply(x, f, y))

totaler = lambda x, y: x + y
running_totaler = running(totaler)
running_running_totaler = running_initial(running_totaler, [])

data = range(0,20)
running_total = reduce(running_totaler, data, [])
running_running_total = reduce(running_running_totaler, data, [])

for i in Zip(data, running_total, running_running_total):
    print "{0:>3}{1:>4}{2:>83}".format(*i)

+演算子があるため、これらは非常に大きなリストでは長い時間がかかります。関数型言語では、正しく実行された場合、このリストの構成はO(n)になります。

出力の最初の数行は次のとおりです。

0   0                      [0]
1   1                   [0, 1]
2   3                [0, 1, 3]
3   6             [0, 1, 3, 6]
4  10         [0, 1, 3, 6, 10]
5  15     [0, 1, 3, 6, 10, 15]
6  21 [0, 1, 3, 6, 10, 15, 21]
0
Cirdec

これは最初から毎回行うので非効率的ですが、可能性は次のとおりです。

a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in Zip(a,runtot):
    print line
0