数字のリストから現在の合計を取得したい。
デモの目的で、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()
を初期化します。リスト内包のような、これに対するよりエレガントな方法はありますか?
リスト内包表記には、作成しているリストそのものを参照するための適切な(クリーンでポータブルな)方法がありません。優れたエレガントなアプローチの1つは、ジェネレーターで作業を行うことです。
_def running_sum(a):
tot = 0
for item in a:
tot += item
yield tot
_
もちろん、これをリストとして取得するには、list(running_sum(a))
を使用します。
numpy を使用できる場合は、これを行うcumsum
という名前の組み込み関数があります。
import numpy
tot = numpy.cumsum(a) # returns a numpy.ndarray
tot = list(tot) # if you prefer a list
これはPythonでは2行で実装できます。
デフォルトのパラメーターを使用すると、外部でaux変数を維持する必要がなくなり、リストに対してmap
を実行するだけです。
def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))
'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倍難しいです。したがって、コードをできるだけ巧妙に書くと、定義上、デバッグするのに十分賢くなりません。」
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 パッケージのバックポートを使用できます。
Bisect_leftを使用できる累積度数を生成するために同じことをしたかったのですが、これがリストの生成方法です。
[ sum( a[:x] ) for x in range( 1, len(a)+1 ) ]
線形時間と空間でのもう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)リストまたはジェネレーターは、ガベージコレクターに大きなストレスを与えるため、これと比較してパフォーマンスが非常に低くなります。
線形時間解のワンライナーは次のとおりです。
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]
は現在の合計値になります。
これにはコルーチンを使用します:
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)]
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)]
この:
total
を0
に初期化します。これは、現在の合計を表します。total := total + x
)でtotal
をインクリメントしますtotal
の新しい値を返しますあなたは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]
これは最初から毎回行うので非効率的ですが、可能性は次のとおりです。
a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in Zip(a,runtot):
print line