28

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

デモの目的で、私は以下を使用して番号の連続リストから始めます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()を繰り返すたびに初期化します。リスト内包のような、これに対するよりエレガントな方法はありますか?

4

13 に答える 13

30

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

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

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

于 2010-08-08T02:28:57.647 に答える
28

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

import numpy as np
tot = np.cumsum(a)  # returns a np.ndarray
tot = list(tot)     # if you prefer a list
于 2010-08-08T02:35:07.077 に答える
12

'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倍難しいのです。したがって、コードをできるだけ巧妙に書くと、定義上、デバッグするのに十分賢くなりません。」

于 2010-08-08T02:28:09.520 に答える
10

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

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

def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))
于 2010-08-08T02:50:18.163 に答える
9

を使用し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)

これはPython3でのみ機能します。Python2では、more-itertoolsパッケージのバックポートを使用できます。

于 2014-08-15T00:43:49.850 に答える
8

リストの合計をとるときは、アキュムレータ(memo)を指定してから、リストをウォークスルーし、各要素とアキュムレータに2進関数「x+y」を適用します。手順的には、これは次のようになります。

def mySum(list):
    memo = 0
    for e in list:
        memo = memo + e
    return memo

これは一般的なパターンであり、合計をとる以外の場合に役立ちます。これを任意のバイナリ関数に一般化して、パラメーターとして提供し、呼び出し元に初期値を指定させることもできます。reduceこれにより、、、foldlまたはinject[1]として知られる関数が得られます。

def myReduce(function, list, initial):
    memo = initial
    for e in list:
        memo = function(memo, e)
    return memo

def mySum(list):
    return myReduce(lambda memo, e: memo + e, list, 0)

Python 2ではreduce組み込み関数でしたが、Python3ではfunctools次のモジュールに移動されました。

from functools import reduce

reduce最初の引数として提供する関数に応じて、あらゆる種類のクールな処理を実行できます。「sum」を「listconcatenation」に置き換え、「zero」を「empty list」に置き換えると、(浅い)copy関数が得られます。

def myCopy(list):
    return reduce(lambda memo, e: memo + [e], list, [])

myCopy(range(10))
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

transformに別のパラメーターとして関数を追加し、copy連結する前にそれを適用すると、次のようになりますmap

def myMap(transform, list):
    return reduce(lambda memo, e: memo + [transform(e)], list, [])

myMap(lambda x: x*2, range(10))
> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

predicateパラメータとしてブール値を返す関数を追加し、eそれを使用して連結するかどうかを決定すると、次のようになりますfilter

def myFilter(predicate, list):
    return reduce(lambda memo, e: memo + [e] if predicate(e) else memo, list, [])

myFilter(lambda x: x%2==0, range(10))
> [0, 2, 4, 6, 8]

mapfilterは、リスト内包表記を書くための一種の不気味な方法[x*2 for x in range(10)]です[x for x in range(10) if x%2==0]。に対応するリスト内包構文はありません。これは、リストを返す必要がまったくないreduceためです(前述のように、Pythonも組み込み関数として提供します)。reducesum

ランニングサムを計算するためのリスト作成機能はreduce、まさに私たちが望んでいるものであり、この問題を解決するためのおそらく最もエレガントな方法lambdaです。そのバージョンはreduce、実行時に古い値のコピーを残す、reductionsまたはscanl[1]と呼ばれ、次のようになります。

def reductions(function, list, initial):
    return reduce(lambda memo, e: memo + [function(memo[-1], e)], list, [initial])

装備が整ったので、次のように定義できます。

def running_sum(list):
    first, rest = list[0], list[1:]
    return reductions(lambda memo, e: memo + e, rest, first)

running_sum(range(10))
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

概念的にはエレガントですが、この正確なアプローチは、Pythonでは実際にはうまくいきません。Pythonlist.append()はリストをその場で変更しますが、それを返さないため、ラムダで効果的に使用することはできず、+代わりに演算子を使用する必要があります。これにより、まったく新しいリストが作成されます。これには、これまでに蓄積されたリストの長さに比例した時間がかかります(つまり、O(n)操作)。forこれを行うときのO(n)ループ内にすでにいるためreduce、全体的な時間計算量はO(n 2)になります。

Ruby [2]のような言語でarray.push eは、変異を返しますがarray、同等の言語はO(n)時間で実行されます。

class Array
  def reductions(initial, &proc)
    self.reduce [initial] do |memo, e|
      memo.push proc.call(memo.last, e)
    end
  end
end

def running_sum(enumerable)
  first, rest = enumerable.first, enumerable.drop(1)
  rest.reductions(first, &:+)
end

running_sum (0...10)
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

JavaScript [2]でも同じですが、そのarray.push(e)戻り値e(ではありませんarray)ですが、無名関数を使用して複数のステートメントを含めることができます。これを使用して、戻り値を個別に指定できます。

function reductions(array, callback, initial) {
    return array.reduce(function(memo, e) {
        memo.push(callback(memo[memo.length - 1], e));
        return memo;
    }, [initial]);
}

function runningSum(array) {
    var first = array[0], rest = array.slice(1);
    return reductions(rest, function(memo, e) {
        return x + y;
    }, first);
}

function range(start, end) {
    return(Array.apply(null, Array(end-start)).map(function(e, i) {
        return start + i;
    }
}

runningSum(range(0, 10));
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

では、ランニングサム関数を作成するためにreductions渡す関数の概念的な単純さを維持しながら、これをどのように解決できるでしょうか。手続き的にlambda x, y: x + y書き直してみましょう。誤って二次reductions問題を修正することができます。問題が発生している間は、ヒープのスラッシングを回避するために結果リストを事前に割り当ててください[3]

def reductions(function, list, initial):
    result = [None] * len(list)
    result[0] = initial
    for i in range(len(list)):
        result[i] = function(result[i-1], list[i])
    return result

def running_sum(list):
    first, rest = list[0], list[1:]
    return reductions(lambda memo, e: memo + e, rest, first)

running_sum(range(0,10))
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

これは私にとってのスイートスポットです。O(n)パフォーマンスであり、最適化された手続き型コードは意味のある名前で隠されており、次に中間値をリストに累積する関数を作成する必要があるときに再利用できます。

  1. 名前はreduce/ reductionsLISPの伝統、foldl/ scanlMLの伝統、およびinjectSmalltalkの伝統に由来します。
  2. PythonListとRubyArrayはどちらも、「動的配列」(またはstd::vectorC ++)と呼ばれる自動的にサイズ変更されるデータ構造の実装です。JavaScriptArrayはもう少しバロック的ですが、範囲外のインデックスに割り当てたり、変更したりしない限り、同じように動作しますArray.length
  3. Pythonランタイムでリストのバッキングストアを形成する動的配列は、リストの長さが2の累乗を超えるたびにサイズを変更します。リストのサイズ変更とは、古いリストの2倍のサイズのヒープに新しいリストを割り当て、古いリストの内容を新しいリストにコピーし、古いリストのメモリをシステムに戻すことを意味します。これはO(n)演算ですが、リストが大きくなるにつれて発生する頻度が少なくなるため、平均的な場合、リストに追加する時間の複雑さはO(1)になります。ただし、古いリストに残された「穴」は、ヒープ内の位置によっては、リサイクルが難しい場合があります。ガベージコレクションと堅牢なメモリアロケータを使用しても、既知のサイズの配列を事前に割り当てることで、基盤となるシステムの作業を節約できます。
于 2015-03-26T07:04:59.177 に答える
4

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

[ sum( a[:x] ) for x in range( 1, len(a)+1 ) ]
于 2012-12-19T20:52:12.870 に答える
3

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

# 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 := total + x
    • 同時にtotal、生成されたマップされたタプルの一部としての新しい値を返します
于 2019-04-27T15:21:26.440 に答える
2

線形時間ソリューションのワンライナーは次のとおりです。

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]の合計値になります。

于 2016-03-16T17:49:49.263 に答える
2

線形時間と空間でのもう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)リストまたはジェネレーターを生成し、ガベージコレクターを強調するからです。これと比較して、パフォーマンスが非常に悪いことが多くあります。

于 2016-11-21T10:40:04.037 に答える
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)]
于 2010-08-08T02:35:26.420 に答える
0

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

a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in zip(a,runtot):
    print line
于 2010-08-08T04:49:49.173 に答える
0

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

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]
于 2010-08-08T06:00:33.773 に答える