Answers:
除+和*外,我为它找到的其他用途是与和和或,但现在我们有any
和all
来替换这些情况。
foldl
并且foldr
确实在Scheme中出现了很多...
这是一些可爱的用法:
整理清单
目标:[[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)
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变得昂贵,而将str和concatenate连接的int保持便宜。
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以及将数字转换为str方法需要0.16 s。
reduce()
可用于查找3个或更多数字的最小公倍数:
#!/usr/bin/env python
from fractions 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
lcm
在第二行?
找到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])
reduce
我在代码中发现的用法涉及以下情况:我具有一些用于逻辑表达式的类结构,因此需要将这些表达式对象的列表转换为表达式的并集。我已经有了一个make_and
给定两个表达式的连接词创建函数,因此我写了reduce(make_and,l)
。(我知道列表不为空;否则它将是类似reduce(make_and,l,make_true)
。)
这正是(某些)函数式程序员喜欢reduce
(或折叠函数,通常称为此类函数)的原因。经常有已经有很多二元函数喜欢+
,*
,min
,max
,级联和,在我的情况,make_and
和make_or
。有一个reduce
它,将这些操作提升到列表(或一般来说是折叠功能的树或其他任何东西)变得微不足道。
当然,如果sum
经常使用某些实例化(例如),那么您就不想继续写作reduce
。但是,除了sum
使用某些for循环定义之外,您还可以使用轻松定义它reduce
。
正如其他人提到的,可读性确实是一个问题。但是,您可能会争辩说,人们发现reduce
“清晰”较少的唯一原因是因为它不是许多人知道和/或使用的功能。
and
运算符的短路行为:L and reduce(make_and, L)
如果在这种情况下返回空列表是合适的
函数组成:如果您已经有了要连续应用的函数列表,例如:
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))))
某种语法更易读和可维护。
您可以替换value = json_obj['a']['b']['c']['d']['e']
为:
value = reduce(dict.__getitem__, 'abcde', json_obj)
如果您已经将路径a/b/c/..
作为列表。例如,使用list中的项目更改嵌套字典的dict中的值。
@Blair Conrad:您也可以使用sum来实现glob / reduce,如下所示:
files = sum([glob.glob(f) for f in args], [])
这比您的两个示例中的任何一个都不那么冗长,完全是Python风格的,并且仍然只是一行代码。
因此,为了回答最初的问题,我个人尝试避免使用reduce,因为它从来没有真正需要过,而且我发现它比其他方法不太清楚。但是,有些人习惯于减少并开始喜欢它来列出理解力(尤其是Haskell程序员)。但是,如果您还没有考虑过reduce的问题,那么您可能不必担心使用它。
sum
并reduce
导致二次行为。可以在线性时间内完成:files = chain.from_iterable(imap(iglob, args))
。尽管在这种情况下,由于时间的关系,glob()访问磁盘可能并不重要。
reduce
当您需要查找类似set
对象序列的并集或交集时,此功能很有用。
>>> reduce(operator.or_, ({1}, {1, 2}, {1, 3})) # union
{1, 2, 3}
>>> reduce(operator.and_, ({1}, {1, 2}, {1, 3})) # intersection
{1}
(除了实际set
的,其中的一个示例是Django的Q对象。)
另一方面,如果要处理bool
s,则应使用any
和all
:
>>> any((True, False, True))
True
重复我的代码后,看来我使用过的reduce唯一要做的就是计算阶乘:
reduce(operator.mul, xrange(1, x+1) or (1,))
我正在为一种语言编写一个compose函数,因此我使用reduce和我的apply运算符来构造该组合函数。
简而言之,compose将一系列函数组合成一个函数。如果我有一个分阶段应用的复杂操作,那么我希望将其全部组合起来,如下所示:
complexop = compose(stage4, stage3, stage2, stage1)
这样,我便可以将其应用于这样的表达式:
complexop(expression)
我希望它等同于:
stage4(stage3(stage2(stage1(expression))))
现在,要构建内部对象,我希望它说:
Lambda([Symbol('x')], Apply(stage4, Apply(stage3, Apply(stage2, Apply(stage1, Symbol('x'))))))
(Lambda类构建用户定义的函数,Apply构建函数应用程序。)
现在,不幸的是,reduce折叠的方向错误,所以我大致使用了:
reduce(lambda x,y: Apply(y, x), reversed(args + [Symbol('x')]))
要弄清楚reduce产生了什么,请在REPL中尝试以下方法:
reduce(lambda x, y: (x, y), range(1, 11))
reduce(lambda x, y: (y, x), reversed(range(1, 11)))
compose = lambda *func: lambda arg: reduce(lambda x, f: f(x), reversed(funcs), arg)
,以生成的功能性能测试所有可能的组合。
reduce可以用来获取第n个元素最大的列表
reduce(lambda x,y: x if x[2] > y[2] else y,[[1,2,3,4],[5,2,5,7],[1,6,0,2]])
将返回[5、2、5、7],因为它是具有最大3rd元素的列表+
Reduce不仅限于标量运算;它也可以用于将事物分类到存储桶中。(这是我最常使用的减少方法)。
想象一下,如果您有一个对象列表,并且想根据对象中平面存储的属性按层次结构对其进行重新组织。在以下示例中,我使用该articles
功能生成了与XML编码报纸中的文章相关的元数据对象列表。articles
生成一个XML元素列表,然后一个一个地映射它们,生成包含一些有趣信息的对象。在前端,我要让用户按节/小节/标题浏览文章。因此,我通常使用reduce
文章列表,并返回一个反映章节/小节/文章层次结构的字典。
from lxml import etree
from Reader import Reader
class IssueReader(Reader):
def articles(self):
arts = self.q('//div3') # inherited ... runs an xpath query against the issue
subsection = etree.XPath('./ancestor::div2/@type')
section = etree.XPath('./ancestor::div1/@type')
header_text = etree.XPath('./head//text()')
return map(lambda art: {
'text_id': self.id,
'path': self.getpath(art)[0],
'subsection': (subsection(art)[0] or '[none]'),
'section': (section(art)[0] or '[none]'),
'headline': (''.join(header_text(art)) or '[none]')
}, arts)
def by_section(self):
arts = self.articles()
def extract(acc, art): # acc for accumulator
section = acc.get(art['section'], False)
if section:
subsection = acc.get(art['subsection'], False)
if subsection:
subsection.append(art)
else:
section[art['subsection']] = [art]
else:
acc[art['section']] = {art['subsection']: [art]}
return acc
return reduce(extract, arts, {})
我在这里给出两个函数,因为我认为它显示了map和reduce在处理对象时如何很好地互补。使用for循环可以完成相同的事情,但是,……花一些严肃的时间使用函数式语言往往会使我在映射和归约方面进行思考。
顺便说一句,如果有人像我在中所做的那样设置属性的更好方法extract
,而您要设置的属性的父级可能还不存在,请告诉我。
不知道这是您要追求的,但是您可以在Google上搜索源代码。
点击链接以搜索“ function:reduce()lang:python”在Google Code搜索上
乍看之下,以下项目使用 reduce()
等),但由于它们是大型项目,因此这些不足为奇。
reduce的功能可以使用函数递归来完成,我想Guido认为它更明确。
更新:
由于Google的代码搜索已于2012年1月15日停产,因此除了恢复常规的Google搜索外,还有一个名为“代码片段集”的代码看起来很有希望。这个(封闭的)问题的答案中提到了许多其他资源。是否可以替换Google Code Search?。
更新2(2017年5月29日):
Nullege搜索引擎是Python示例(使用开源代码)的一个很好的来源。
for
循环。
lang:python "reduce("
也会reduce
根据源代码的编码样式找到的定义。
import os
files = [
# full filenames
"var/log/apache/errors.log",
"home/kane/images/avatars/crusader.png",
"home/jane/documents/diary.txt",
"home/kane/images/selfie.jpg",
"var/log/abc.txt",
"home/kane/.vimrc",
"home/kane/images/avatars/paladin.png",
]
# unfolding of plain filiname list to file-tree
fs_tree = ({}, # dict of folders
[]) # list of files
for full_name in files:
path, fn = os.path.split(full_name)
reduce(
# this fucction walks deep into path
# and creates placeholders for subfolders
lambda d, k: d[0].setdefault(k, # walk deep
({}, [])), # or create subfolder storage
path.split(os.path.sep),
fs_tree
)[1].append(fn)
print fs_tree
#({'home': (
# {'jane': (
# {'documents': (
# {},
# ['diary.txt']
# )},
# []
# ),
# 'kane': (
# {'images': (
# {'avatars': (
# {},
# ['crusader.png',
# 'paladin.png']
# )},
# ['selfie.jpg']
# )},
# ['.vimrc']
# )},
# []
# ),
# 'var': (
# {'log': (
# {'apache': (
# {},
# ['errors.log']
# )},
# ['abc.txt']
# )},
# [])
#},
#[])
我曾经用sqlalchemy-searchable中的运算符reduce
来连接PostgreSQL搜索向量列表||
:
vectors = (self.column_vector(getattr(self.table.c, column_name))
for column_name in self.indexed_columns)
concatenated = reduce(lambda x, y: x.op('||')(y), vectors)
compiled = concatenated.compile(self.conn)
我有一个老式的pipegrep Python实现,该实现使用reduce和glob模块来构建要处理的文件列表:
files = []
files.extend(reduce(lambda x, y: x + y, map(glob.glob, args)))
当时我觉得很方便,但实际上没有必要,因为类似的东西一样好,而且可读性更高
files = []
for f in args:
files.extend(glob.glob(f))
files = [glob.glob(f) for f in args]
itertools
使用docs.python.org/library/itertools.html中的flatten()
配方import,然后编写:(files = flatten(glob.glob(f) for f in args)
这一次,我在发布代码之前对其进行了测试,并且我知道这可以正常工作。)
files = chain.from_iterable(imap(iglob, args))
其中chain
,imap
来自itertools
模块,glob.iglob
如果从模式args
产生多个目录中的文件,则很有用。
假设有一些年度统计数据存储在“计数器”列表中。我们想要查找不同年份中每个月的MIN / MAX值。例如,对于1月将是10。对于2月将是15。我们需要将结果存储在新的Counter中。
from collections import Counter
stat2011 = Counter({"January": 12, "February": 20, "March": 50, "April": 70, "May": 15,
"June": 35, "July": 30, "August": 15, "September": 20, "October": 60,
"November": 13, "December": 50})
stat2012 = Counter({"January": 36, "February": 15, "March": 50, "April": 10, "May": 90,
"June": 25, "July": 35, "August": 15, "September": 20, "October": 30,
"November": 10, "December": 25})
stat2013 = Counter({"January": 10, "February": 60, "March": 90, "April": 10, "May": 80,
"June": 50, "July": 30, "August": 15, "September": 20, "October": 75,
"November": 60, "December": 15})
stat_list = [stat2011, stat2012, stat2013]
print reduce(lambda x, y: x & y, stat_list) # MIN
print reduce(lambda x, y: x | y, stat_list) # MAX
我只是发现了有用的用法reduce
:在不删除定界符的情况下拆分字符串。该代码完全来自“编程口语”博客。这是代码:
reduce(lambda acc, elem: acc[:-1] + [acc[-1] + elem] if elem == "\n" else acc + [elem], re.split("(\n)", "a\nb\nc\n"), [])
结果如下:
['a\n', 'b\n', 'c\n', '']
请注意,它处理的是在SO中无法普遍解决的极端情况。有关更深入的解释,我将您重定向到原始博客文章。
使用reduce()来确定日期列表是否连续:
from datetime import date, timedelta
def checked(d1, d2):
"""
We assume the date list is sorted.
If d2 & d1 are different by 1, everything up to d2 is consecutive, so d2
can advance to the next reduction.
If d2 & d1 are not different by 1, returning d1 - 1 for the next reduction
will guarantee the result produced by reduce() to be something other than
the last date in the sorted date list.
Definition 1: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider consecutive
Definition 2: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider not consecutive
"""
#if (d2 - d1).days == 1 or (d2 - d1).days == 0: # for Definition 1
if (d2 - d1).days == 1: # for Definition 2
return d2
else:
return d1 + timedelta(days=-1)
# datelist = [date(2014, 1, 1), date(2014, 1, 3),
# date(2013, 12, 31), date(2013, 12, 30)]
# datelist = [date(2014, 2, 19), date(2014, 2, 19), date(2014, 2, 20),
# date(2014, 2, 21), date(2014, 2, 22)]
datelist = [date(2014, 2, 19), date(2014, 2, 21),
date(2014, 2, 22), date(2014, 2, 20)]
datelist.sort()
if datelist[-1] == reduce(checked, datelist):
print "dates are consecutive"
else:
print "dates are not consecutive"
from functools import reduce
允许相同的代码工作在两个Python 2和3