Я хочу получить промежуточную сумму из списка чисел.
В демонстрационных целях я начну с последовательного списка чисел, используя 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 ()
в каждой итерации цикла. Есть ли более элегантный способ для этого, например, для понимания списка?
У понимания списка нет хорошего (чистого, переносимого) способа ссылаться на сам список, который он строит. Одним из хороших и элегантных подходов может быть выполнение работы в генераторе:
def running_sum(a):
tot = 0
for item in a:
tot += item
yield tot
чтобы получить это в виде списка, конечно, используйте list(running_sum(a))
.
В Python это можно реализовать в 2 строки.
Использование параметра по умолчанию устраняет необходимость поддерживать внешнюю вспомогательную переменную, а затем мы просто делаем отображение
в список.
def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))
Я бы использовал для этого сопрограмму:
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)]
Это неэффективно, так как это происходит каждый раз с самого начала, но возможно, что это так:
a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in zip(a,runtot):
print line
Если вы можете использовать numpy, в нем есть встроенная функция cumsum
, которая делает следующее.
import numpy
tot = numpy.cumsum(a) # returns a numpy.ndarray
tot = list(tot) # if you prefer a list
Я не уверен насчет "элегантности", но я думаю, что следующее намного проще и интуитивно понятнее (ценой дополнительной переменной):
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])
... но я все еще нахожу это менее понятным, чем мое первоначальное предложение.
Вспомните слова Кернигана: "Отладка вдвое сложнее, чем написание кода. Поэтому, если вы пишете код настолько умно, насколько это возможно, вы, по определению, недостаточно умны для его отладки"
.Вам нужны две вещи: сворачивание (сокращение) и забавная функция, которая хранит список результатов другой функции, которую я назвал запущенной. Я делал версии как с начальным параметром, так и без него; в любом случае они должны быть сокращены с начальным [].
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]