29 Stimmen

Listenverständnis für laufende Summe

Ich möchte eine laufende Summe aus einer Liste von Zahlen erhalten.

Zu Demonstrationszwecken beginne ich mit einer fortlaufenden Liste von Zahlen mit 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)

ergibt

  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

Wie Sie sehen können, initialisiere ich eine leere Liste [] entonces append() in jeder Schleifeniteration. Gibt es einen eleganteren Weg zu diesem, wie eine Liste Verständnis?

30voto

Alex Martelli Punkte 805329

Ein Listenverständnis hat keinen guten (sauberen, portablen) Weg, um auf die Liste selbst zu verweisen, die es erstellt. Ein guter und eleganter Ansatz könnte darin bestehen, die Aufgabe in einem Generator zu erledigen:

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

um dies als Liste zu erhalten, verwenden Sie stattdessen natürlich list(running_sum(a)) .

3 Stimmen

Unter Python 3 sollten Sie itertools.accumulate(a)

28voto

Mr Fooz Punkte 102791

Wenn Sie Folgendes verwenden können numpy hat es eine eingebaute Funktion namens cumsum das dies tut.

import numpy as np
tot = np.cumsum(a)  # returns a np.ndarray
tot = list(tot)     # if you prefer a list

12voto

pjz Punkte 39845

Ich bin mir nicht sicher, ob das "elegant" ist, aber ich denke, dass die folgende Lösung viel einfacher und intuitiver ist (auf Kosten einer zusätzlichen Variablen):

a = range(20)

runningTotal = []

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

Die funktionelle Art, dasselbe zu tun, ist:

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

...aber das ist viel weniger lesbar/pflegbar, etc.

@Omnifarous schlägt vor, dass dies verbessert werden sollte:

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

...aber ich finde das immer noch weniger unmittelbar nachvollziehbar als meinen ersten Vorschlag.

Erinnern Sie sich an die Worte von Kernighan: "Das Debuggen ist doppelt so schwer wie das Schreiben des Codes selbst. Wenn Sie also den Code so clever wie möglich schreiben, sind Sie per Definition nicht clever genug, um ihn zu debuggen."

1 Stimmen

+1 für das Debugging-Zitat, das die Unlesbarkeit des reduce-Beispiels hervorhebt :)

1 Stimmen

Ich hätte die reduce comme reduce(lambda l, v: (l.append(l[-1] + v) or l), a, [0])

0 Stimmen

@Satoru.Logic - Ich denke, die Entlassung reduce indem man den Code absichtlich undurchsichtiger macht, als er sein muss, ist ziemlich unaufrichtig. Ich denke auch, dass man sich ein wenig an die Parteilinie hält, dass Reduzieren unheimlich ist und man nicht funktional in Python programmieren sollte.

10voto

satoru Punkte 29284

Dies kann in 2 Zeilen in Python implementiert werden.

Durch die Verwendung eines Standardparameters entfällt die Notwendigkeit, eine Aux-Variable außerhalb zu pflegen, und wir führen lediglich eine map in die Liste aufzunehmen.

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

4 Stimmen

Damit wird eine Funktion von Python "ausgenutzt", die mir schon einmal einen Strich durch die Rechnung gemacht hat. Ich mag es, fürchte aber, dass es eine böse Falle darstellt, wenn der entsprechende Code jemals debuggt werden muss!

5 Stimmen

Eher 4 PEP-8 Zeilen :)

2 Stimmen

Eine offizielle "accumulate"-Funktion ist nun in Python 3 verfügbar als from itertools import accumulate . Auch die Implementierung von Satorus "Akkumulieren" ist zwar clever, bricht aber ab, sobald Sie versuchen, es ein zweites Mal auszuführen.

9voto

mleyfman Punkte 600

Utilice itertools.accumulate() . Hier ist ein Beispiel:

from itertools import accumulate

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

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

Dies funktioniert nur mit Python 3. Unter Python 2 können Sie den Backport in der mehr-itertools Paket.

1 Stimmen

Dies ist eine alte Frage mit vielen alten Antworten, aber im Jahr 2015 ist dies die beste Lösung.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X