Эквивалент Haskell scanl в python - программирование
Подтвердить что ты не робот

Эквивалент Haskell scanl в python

Я хотел бы знать, есть ли встроенная функция в python для эквивалентного Haskell scanl, поскольку reduce является эквивалентом foldl.

Что-то, что делает это:

Prelude> scanl (+) 0 [1 ..10]
[0,1,3,6,10,15,21,28,36,45,55]

Вопрос не в том, как его реализовать, у меня уже есть 2 реализации, как показано ниже (однако, если у вас есть более элегантный, пожалуйста, не стесняйтесь показать его здесь).

Первая реализация:

 # Inefficient, uses reduce multiple times
 def scanl(f, base, l):
   ls = [l[0:i] for i in range(1, len(l) + 1)]
   return [base] + [reduce(f, x, base) for x in ls]

  print scanl(operator.add, 0, range(1, 11))

дает:

[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

Вторая реализация:

 # Efficient, using an accumulator
 def scanl2(f, base, l):
   res = [base]
   acc = base
   for x in l:
     acc = f(acc, x)
     res += [acc]
   return res

 print scanl2(operator.add, 0, range(1, 11))

дает:

[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

Спасибо:)

4b9b3361

Ответ 1

Вы можете использовать это, если его более элегантно:

def scanl(f, base, l):
    for x in l:
        base = f(base, x)
        yield base

Используйте его как:

import operator
list(scanl(operator.add, 0, range(1,11)))

Python 3.x имеет itertools.accumulate(iterable, func= operator.add). Он выполняется, как показано ниже. Реализация может дать вам идеи:

def accumulate(iterable, func=operator.add):
    'Return running totals'
    # accumulate([1,2,3,4,5]) --> 1 3 6 10 15
    # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
    it = iter(iterable)
    total = next(it)
    yield total
    for element in it:
        total = func(total, element)
        yield total

Ответ 2

У меня была аналогичная потребность. Эта версия использует понимание списка python

def scanl(data):
    '''
    returns list of successive reduced values from the list (see haskell foldl)
    '''
    return [0] + [sum(data[:(k+1)]) for (k,v) in enumerate(data)]


>>> scanl(range(1,11))

дает:

[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

Ответ 3

Как обычно, экосистема Python также переполнена решениями:

В инструменте есть накопитель, способный использовать функцию, предоставленную пользователем, в качестве аргумента. Я тестировал его с помощью лямбда-выражений.

https://github.com/pytoolz/toolz/blob/master/toolz/itertoolz.py

https://pypi.python.org/pypi/toolz

как и more_itertools

http://more-itertools.readthedocs.io/en/stable/api.html

Я не тестировал версию из более-itertools, но также может выполнять пользовательскую функцию.

Ответ 4

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (:= оператор), который дает возможность назвать результат выражения, мы можем использовать понимание списка для репликации операции сканирования влево:

acc = 0
scanned = [acc := acc + x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 3, 6, 10, 15]

Или в общем виде, учитывая список, сокращающую функцию и инициализированный аккумулятор:

items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc + x
accumulator = 0

мы можем сканировать items слева и уменьшить их с помощью f:

scanned = [accumulator := f(accumulator, x) for x in items]
# scanned = [1, 3, 6, 10, 15]