什么是';pythonic';相当于';折叠';函数编程中的函数?

什么是';pythonic';相当于';折叠';函数编程中的函数?,python,list,functional-programming,reduce,fold,Python,List,Functional Programming,Reduce,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的一个实现,与上面完全相同,但是,我被告知“pythonic”编程方式是避免lambda术语和高阶函数,在可能的情况下更倾向于列表理解。因此,在Python中,是否有一种折叠列表或类似列表的结构的首选方

在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的一个实现,与上面完全相同,但是,我被告知“pythonic”编程方式是避免
lambda
术语和高阶函数,在可能的情况下更倾向于列表理解。因此,在Python中,是否有一种折叠列表或类似列表的结构的首选方法不是
reduce
函数,或者
reduce
是实现这一点的惯用方法?

对数组求和的Python方法正在使用。出于其他目的,您有时可以使用(来自模块)和模块的某些组合,例如:

def product(xs):
    return reduce(operator.mul, xs, 1)
请注意,
reduce
实际上是一个
foldl
,用Haskell的话来说。没有特殊的语法来执行折叠,没有内置的
foldr
,并且实际使用
reduce
和非关联运算符被认为是不好的风格

使用高阶函数是非常类似于python的;它充分利用了Python的原理,即所有东西都是对象,包括函数和类。你是对的,一些蟒蛇学家不喜欢lambda,但主要是因为当它们变得复杂时,它们的可读性不强。

这个(reduce)问题的实际答案是:只使用一个循环

initial_value = 0
for x in the_list:
    initial_value += x #or any function.
这将比reduce更快,像PyPy这样的东西可以优化这样的循环


顺便说一句,总和的情况应该用函数来解决,你也可以重新发明轮子:

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's elements
print "Count(x==True):", 
print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0)
哈斯克尔

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

蟒蛇

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

显然,这只是一个简单的例子来说明这一点。在Python中,您只需执行
sum([1,2,3,4,5])
,甚至Haskell纯粹主义者通常也更喜欢
sum[1,2,3,4,5]

对于没有明显方便函数的非平凡场景,惯用的pythonic方法是显式写出For循环并使用可变变量赋值,而不是使用
reduce
fold


这根本不是功能性风格,而是“pythonic”风格。Python不是为函数纯粹主义者设计的。查看Python如何支持流控制的异常,查看Python的非功能性惯用用法。

这并不是问题的真正答案,而是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

在Python 3中,
reduce
已被删除:。不过,您可以使用

另一方面,文档表示首选
for
-循环,而不是
reduce
,因此:

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

我可能会迟到,但我们可以使用简单的lambda演算和curried函数创建自定义
foldr
。下面是我在python中对foldr的实现

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

我相信这个问题的一些回答者忽略了
折叠功能作为抽象工具的更广泛含义。是的,
sum
可以对整数列表执行相同的操作,但这只是一个小问题<代码>折叠
更通用。当您拥有一系列形状各异的数据结构并希望清晰地表示聚合时,它非常有用。因此,不必使用聚合变量为构建一个
循环并每次手动重新计算它,一个
fold
函数(或Python版本,它似乎与
reduce
相对应)允许程序员通过简单地提供两件事来更清楚地表达聚合的意图:

  • 聚合的默认起始值或“种子”值
  • 一个函数,它获取聚合的当前值(以“种子”开头)和列表中的下一个元素,并返回下一个聚合值

Python 3.8开始,引入(
:=
运算符),可以命名表达式的结果,我们可以使用列表理解来复制其他语言称为fold/foldleft/reduce的操作:

给定一个列表、一个缩减函数和一个累加器:

items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1
我们可以使用
f
折叠
项目
,以获得所产生的
累积

[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

sum
不够好吗?不确定这是否是你问题的好例子。使用
sum
可以很容易地实现这一点,您可能需要提供一些不同类型的示例。嘿,JBernardo-对数字列表求和是一个相当简单的示例,我更感兴趣的是使用一些二进制运算和起始值来累积列表元素的一般想法,不是专门对整数求和。@mistertim:
sum()
实际上提供了有限的功能<例如,code>sum([[a],[b,c,d],[e,f]],[])
返回
[a,b,c,d,e,f]
。虽然使用列表进行此操作是使用此技术需要注意的一个很好的示例-
+
列表在时间和内存方面都是线性时间操作,使得整个调用都是二次的。使用
列表(itertools.chain.from_iterable([a],[b,c,d],[e,f],]))
总体上是线性的-如果你只需要对它迭代一次,你可以放弃对
list
的调用,使它在内存方面保持不变。@JBernardo:你是说任何不在内置模块中的东西都不是pythonic的?不,这说起来很愚蠢。但请给出一个原因
acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120