利用reduce()函数的有用代码?

123

1
from functools import reduce 允许相同的代码在 Python 2 和 3 上运行。 - jfs
24个回答

66
除了加法和乘法,我发现它还可以用于 and 和 or,但现在我们有了 anyall 来替代这些情况。
在 Scheme 中,foldlfoldr 经常出现...
以下是一些巧妙的用法: 打平一个列表 目标:将 [[1, 2, 3], [4, 5], [6, 7, 8]] 转换为 [1, 2, 3, 4, 5, 6, 7, 8]
reduce(list.__add__, [[1, 2, 3], [4, 5], [6, 7, 8]], [])

将数字列表转换为数字

目标:将[1, 2, 3, 4, 5, 6, 7, 8]转换为12345678

低效、繁琐的方式:

int("".join(map(str, [1,2,3,4,5,6,7,8])))

简洁的 reduce 方法:

reduce(lambda a,d: 10*a+d, [1,2,3,4,5,6,7,8], 0)

24
为了将一个嵌套的列表展平,我建议使用 list(itertools.chain(*nested_list)) 这个函数。 - Roberto Bonvallet
13
返回翻译后的文本:sum([[1, 2, 3], [4, 5], [6, 7, 8]], []) - Gordon Wrigley
3
它也对位运算很有用。如果你想对一堆数字执行按位或运算,例如如果需要将列表中的标志转换为位掩码,该怎么办? - Antimony
6
经过一些基准测试,对于较大的列表,“丑陋”的方法速度更快。timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)需要约0.09秒,而timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)需要0.36秒(大约慢4倍)。基本上,当列表变得大时,乘以10会变得昂贵,而int转换为str和连接则保持廉价。 - dr jimbob
3
对于小列表(大小为10),使用reduce方法可以比常规循环快1.3倍。然而,即使在这种情况下,避免使用reduce并执行简单的循环会更快timeit.repeat('convert_digit_list_to_int(digit_list)', setup = 'digit_list = [d%10 for d in xrange(1,10)]\ndef convert_digit_list_to_int(digits):\n i = 0\n for d in digits:\n i = 10*i + d\n return i', number=100000)需要0.06 s,timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,10))', number=100000)需要0.12 s,将数字转换为字符串的方法需要0.16 s。 - dr jimbob
显示剩余13条评论

51

reduce()可以用来找到3个或更多数字的最小公倍数

#!/usr/bin/env python
from math import gcd
from functools import reduce

def lcm(*args):
    return reduce(lambda a,b: a * b // gcd(a, b), args)

示例:

>>> lcm(100, 23, 98)
112700
>>> lcm(*range(1, 20))
232792560

1
第二行中的“lcm”是什么? - beardc
1
@BirdJaguarIV:跟随链接中的答案。lcm()返回两个数字的最小公倍数。 - jfs

40

可以使用reduce()解析点分名称(当使用eval()不安全时):

>>> import __main__
>>> reduce(getattr, "os.path.abspath".split('.'), __main__)
<function abspath at 0x009AB530>

请问,“点分名称”是什么意思?为什么它在__main__上运行?您的示例不够通用,无法处理“字符串”属性。 - Mabadai
@Mabadai:__main__是一个代表你想要获取属性的对象的替身。在这个例子中,它是一个模块对象(repl执行的当前模块)。只要它具有所需的属性,它可以是任何你喜欢的对象。 - jfs

24

查找N个给定列表的交集:

input_list = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7]]

result = reduce(set.intersection, map(set, input_list))

返回:

result = set([3, 4, 5])

来源:Python - Intersection of two lists


1
请查看我对此回答的评论:https://dev59.com/wnRB5IYBdhLWcg3wXmRI#oHcGoYgBc1ULPQZFYVOB - jfs

13

我认为reduce命令很傻。因此:

reduce(lambda hold,next:hold+chr(((ord(next.upper())-65)+13)%26+65),'znlorabggbbhfrshy','')

1
我也喜欢这里的讽刺意味。 - Roman

11

函数组合:如果您已经有了一系列希望按顺序应用的函数,例如:

color = lambda x: x.replace('brown', 'blue')
speed = lambda x: x.replace('quick', 'slow')
work = lambda x: x.replace('lazy', 'industrious')
fs = [str.lower, color, speed, work, str.title]

然后,您可以依次应用它们:

>>> call = lambda s, func: func(s)
>>> s = "The Quick Brown Fox Jumps Over the Lazy Dog"
>>> reduce(call, fs, s)
'The Slow Blue Fox Jumps Over The Industrious Dog'

在这种情况下,方法链可能更易读。但有时不可能使用方法链,这种组合方式可能比 f1(f2(f3(f4(x)))) 这种语法更易读且更易维护。


2
一个优点是你可以在代码中更改要应用的函数列表。 - crlb

11
我在代码中使用了reduce函数,它用于将逻辑表达式的类结构体列表转换为这些表达式的并集。我已经有一个make_and函数,可以将两个表达式合并为一个,并且我写了reduce(make_and, l)来实现这一点。(我知道列表不为空;否则就要像reduce(make_and,l,make_true)那样处理。)
这正是(某些)函数式程序员喜欢reduce(或通常被称为“fold”函数的函数)的原因之一。通常已经有许多像+*minmax、连接以及在我的情况下的make_andmake_or这样的二元函数。拥有reduce函数可以轻松将这些操作提升到列表(或树等其他任何结构,对于折叠函数而言)。
当然,如果特定的实例(例如sum)经常使用,那么您不想一直编写reduce。但是,与其使用某些for循环定义sum,不如使用reduce同样容易地定义它。
正如其他人所提到的那样,可读性确实是一个问题。不过,您可以认为人们认为reduce不太“清晰”的唯一原因是这不是很多人知道和/或使用的函数。

为了防范空列表,您可以利用and运算符的短路行为:L and reduce(make_and, L) 如果在这种情况下返回空列表是适当的。 - jfs

8

您可以使用以下代码替换 value = json_obj['a']['b']['c']['d']['e']

value = reduce(dict.__getitem__, 'abcde', json_obj)

如果您已经将路径 a/b/c/.. 作为列表存在。例如,使用列表中的项更改嵌套字典的值

7

@Blair Conrad:你也可以使用sum来实现你的glob/reduce,代码如下:

files = sum([glob.glob(f) for f in args], [])

这种方法比你提供的两个例子都更简洁,完全符合Python风格,而且仍然只有一行代码。

所以,回答原始问题,我个人尽量避免使用reduce,因为它从来不是真正必要的,而且我发现它比其他方法不太清晰。然而,有些人习惯于使用reduce,并且更喜欢它而不是列表推导式(尤其是Haskell程序员)。但如果你还没有考虑过用reduce解决问题,那么你可能不需要担心使用它。


2
sumreduce都会导致二次行为。可以在线性时间内完成:files = chain.from_iterable(imap(iglob, args))。虽然由于glob()访问磁盘所需的时间,这在本例中可能并不重要。 - jfs

6

reduce可以用于支持链式属性查找:

reduce(getattr, ('request', 'user', 'email'), self)

当然,这等同于
self.request.user.email

但当你的代码需要接受任意数量的属性时,这很有用。
(在处理Django模型时,常见的是任意长度的链接属性。)

如何使用方法而不是属性来应用这种方法? - Marcin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接