Что такое "pythonic", эквивалентный функции "fold" от функционального программирования?

Каков самый идиоматический способ добиться в Haskell чего-то вроде следующего:

foldl (+) 0 [1,2,3,4,5]
--> 15

Или его эквивалент в Ruby:

[1,2,3,4,5].inject(0) {|m,x| m + x}
#> 15

Очевидно, что Python предоставляет функцию reduce, которая представляет собой реализацию fold, точно так же, как указано выше, однако мне сказали, что "питонический" способ программирования состоит в том, чтобы избежать терминов lambda и функций более высокого порядка, предпочитая, если это возможно, понимание списков. Поэтому существует ли предпочтительный способ сгибания списка или структуры списка в Python, которая не является функцией reduce, или является reduce идиоматическим способом достижения этого?

Ответ 1

Pythonic способ суммирования массива использует sum. Для других целей иногда можно использовать некоторую комбинацию из reduce (из модуля functools) и модуля operator, например:

def product(xs):
    return reduce(operator.mul, xs, 1)

Имейте в виду, что reduce на самом деле является foldl, с точки зрения Хаскелла. Нет специального синтаксиса для выполнения свертывания, нет встроенного foldr, и фактически использование reduce с неассоциативными операторами считается плохим стилем.

Использование функций высшего порядка довольно питонно; в нем хорошо используется принцип Python, согласно которому все является объектом, включая функции и классы. Вы правы в том, что некоторые питонисты не одобряют лямбды, но в основном потому, что они, как правило, не очень читабельны, когда становятся сложными.

Ответ 2

Хаскел

foldl (+) 0 [1,2,3,4,5]

Python

reduce(lambda a,b: a+b, [1,2,3,4,5], 0)

Очевидно, что это тривиальный пример, иллюстрирующий точку. В Python вы просто делаете sum([1,2,3,4,5]), и даже пуристы Haskell обычно предпочитают sum [1,2,3,4,5].

Для нетривиальных сценариев, когда нет очевидной удобной функции, идиоматический пифонический подход заключается в том, чтобы явно выписать цикл for и использовать переменное переменное переменное вместо использования reduce или fold.

Это вовсе не функциональный стиль, но это "питонический" способ. Python не предназначен для функциональных пуристов. Посмотрите, как Python поддерживает исключения для управления потоком, чтобы увидеть, как нефункциональный идиоматический питон.

Ответ 3

В Python 3 reduce было удалено: Примечания к выпуску. Тем не менее вы можете использовать модуль functools

import operator, functools
def product(xs):
    return functools.reduce(operator.mul, xs, 1)

С другой стороны, в документации выражает предпочтение в сторону for -loop вместо того, чтобы reduce, следовательно:

def product(xs):
    result = 1
    for i in xs:
        result *= i
    return result

Ответ 4

Вы также можете изобрести колесо:

def fold(f, l, a):
    """
    f: the function to apply
    l: the list to fold
    a: the accumulator, who is also the 'zero' on the first call
    """ 
    return a if(len(l) == 0) else fold(f, l[1:], f(a, l[0]))

print "Sum:", fold(lambda x, y : x+y, [1,2,3,4,5], 0)

print "Any:", fold(lambda x, y : x or y, [False, True, False], False)

print "All:", fold(lambda x, y : x and y, [False, True, False], True)

# Prove that result can be of a different type of the list elements
print "Count(x==True):", 
print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0)

Ответ 5

Не отвечайте на вопрос, но однострочные для foldl и foldr:

a = [8,3,4]

## Foldl
reduce(lambda x,y: x**y, a)
#68719476736

## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L

Ответ 6

Фактический ответ на эту проблему (уменьшение): просто используйте цикл!

initial_value = 0
for x in the_list:
    initial_value += x #or any function.

Это будет быстрее, чем сокращение, и такие вещи, как PyPy, могут оптимизировать такие циклы.

BTW, сумма должна быть решена с помощью функции sum

Ответ 7

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

Приведены список, редукционная функция и аккумулятор:

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

мы можем сложить items с f для получения результирующего accumulation:

[accumulator := f(accumulator, x) for x in items]
# accumulator = 120

или в сгущенном виде:

acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120

Обратите внимание, что на самом деле это также операция "scanleft", так как результат понимания списка представляет состояние накопления на каждом шаге:

acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120

Ответ 8

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

def foldr(func):
    def accumulator(acc):
        def listFunc(l):
            if l:
                x = l[0]
                xs = l[1:]
                return func(x)(foldr(func)(acc)(xs))
            else:
                return acc
        return listFunc
    return accumulator  


def curried_add(x):
    def inner(y):
        return x + y
    return inner

def curried_mult(x):
    def inner(y):
        return x * y
    return inner

print foldr(curried_add)(0)(range(1, 6))
print foldr(curried_mult)(1)(range(1, 6))

Хотя реализация является рекурсивной (может быть медленной), она выведет значения 15 и 120 соответственно.

Ответ 9

Я полагаю, что некоторые из респондентов этого вопроса упустили более широкое значение функции fold как абстрактного инструмента. Да, sum может сделать то же самое для списка целых чисел, но это тривиальный случай. fold более общий. Это полезно, когда у вас есть последовательность структур данных различной формы и вы хотите четко выразить агрегацию. Таким образом, вместо того, чтобы создавать цикл for с агрегатной переменной и вручную каждый раз пересчитывать его, функция fold (или версия Python, reduce которой, по-видимому, соответствует) позволяет программисту выражать намерение агрегации гораздо более просто. просто предоставив две вещи:

  • Начальное или начальное значение по умолчанию для агрегации.
  • Функция, которая принимает текущее значение агрегации (начиная с "семени") и следующего элемента в списке и возвращает следующее значение агрегации.