Python 中的列表理解替代 reduce()
List comprehension as substitute for reduce() in Python
下面的python教程是这样说的:
List comprehension is a complete substitute for the lambda function as well as the functions map()
, filter()
and reduce()
.
但是,它没有提到列表推导式如何替代 reduce()
的示例,我想不出它应该如何实现的示例。
有人可以解释一下如何通过列表理解实现类似 reduce 的功能,或者确认这是不可能的吗?
理想情况下,列表理解是创建一个新列表。引用 official documentation,
List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
而 reduce
用于将可迭代对象减少为单个值。引用 functools.reduce
,
Apply function of two arguments cumulatively to the items of sequence, from left to right, so as to reduce the sequence to a single value.
因此,列表理解不能用作 drop-in 替代 reduce
。
起初我很惊讶 Python 的创建者 Guido van Rossum 反对 reduce
。他的推理是,除了求和、乘法、and-ing 和 or-ing 之外,使用 reduce
会产生一个不可读的解决方案,该解决方案更适合迭代并更新累加器的函数。他关于此事的文章是here。所以不,没有 reduce
的列表理解替代方法,"pythonic" 方法是以老式的方式实现累积函数:
而不是:
out = reduce((lambda x,y: x*y),[1,2,3])
使用:
def prod(myList):
out = 1
for el in myList:
out *= el
return out
当然没有什么可以阻止您继续使用 reduce
(python 2) 或 functools.reduce
(python 3)
列表理解应该return 列表。如果你的 reduce 应该是 return 一个列表,那么是的,你可以用列表理解替换它。
但这并不妨碍提供"reduce-like functionality"。 Python 列表可以包含任何对象。如果您接受包含在单项列表中的结果,那么有一个 [...][0]
列表理解形式可以替换任何 reduce()
。
这应该是显而易见的,但是那个形式是
[x for x in [reduce(function, sequence, initial)]][0]
对于一些二进制 function
和一些可迭代的 sequence
和一些 initial
值。或者,如果您想要来自第一个可迭代对象的 initial
,
[x for x in [reduce(function, sequence)]][0]
可以说,以上是作弊,而且毫无意义,因为你可以在没有理解的情况下使用 reduce
。因此,让我们在没有 reduce
.
的情况下尝试一下
[stack.append(function(stack.pop(), e)) or stack[0]
for stack in ([initial],)
for e in sequence][-1]
这会生成所有中间值的列表,我们需要最后一个。 [-1]
和 [0]
一样简单。我们需要一个累加器来减少,但不能在理解中使用赋值语句,因此 stack
(这只是一个列表),但我们可以在这里使用许多其他数据结构。 .append()
总是 returns None
,所以我们使用 or stack[0]
将到目前为止的值放入结果列表中。
没有initial
,
有点难
[stack.append(function(stack.pop(), e)) or stack[0]
for it in [iter(sequence)]
for stack in [[next(it)]]
for e in it][-1]
真的,此时您不妨使用 for
语句。
但这会占用中间值列表的内存。对于很长的序列,这可能是个问题。但是我们也可以通过使用生成器表达式来避免这种情况。
这样做很棘手,所以让我们从一个更简单的示例开始,然后逐步完成。
stack = [initial]
[stack.append(function(stack.pop(), e)) for e in sequence]
stack.pop() # returns the answer
它会计算答案,但也会创建一个无用的 None
列表。我们可以通过将其转换为列表理解内的生成器表达式来避免这种情况。
stack = [initial]
[_ for _s in (stack.append(function(stack.pop(), e)) or ()
for e in sequence)
for _ in _s]
stack.pop()
列表理解耗尽了更新堆栈的生成器,但return它本身是一个空列表。这是可能的,因为内循环总是有零次迭代,因为 _s
总是一个空元组。
如果最后一个 _s
有一个元素,我们可以将 stack.pop()
移到里面。不过,该元素是什么并不重要。所以我们将 [None]
链接为最终的 _s
.
from itertools import chain
stack = [initial]
[stack.pop()
for _s in chain((stack.append(function(stack.pop(), e)) or ()
for e in sequence),
[[None]])
for _ in _s][0]
同样,我们有一个单项列表理解。我们还可以将 chain
实现为生成器表达式。您已经了解了如何使用单项列表将 stack
变量移入内部。
[stack.pop()
for stack in [[initial]]
for _s in (
x
for xs in [
(stack.append(function(stack.pop(), e)) or ()
for e in sequence),
[[None]],
]
for x in xs)
for _ in _s][0]
而且我们还可以从两个参数的序列中得到初始值reduce
。
[stack.pop()
for it in [iter(sequence)]
for stack in [[next(it)]]
for _s in (
x
for xs in [
(stack.append(function(stack.pop(), e)) or ()
for e in it),
[[None]],
]
for x in xs)
for _ in _s][0]
这太疯狂了。但它有效。所以是的,可能 得到 "reduce-like functionality" 的理解。这并不意味着您 应该 。七个 for
s 太难了!
你可以通过使用我命名为 last
和 cofold
:
的几个辅助函数来完成类似 reduce 的理解
>>> last(r(a+b) for a, b, r in cofold(range(10)))
45
这在功能上等同于
>>> reduce(lambda a, b: a+b, range(10))
45
请注意,与 reduce()
不同,理解没有使用 lambda
。
诀窍是使用带有回调的生成器 "return" 运算符的结果。 cofold
是 reduce(或折叠)函数的 corecursive dual。
_sentinel = object()
def cofold(it, initial=_sentinel):
if initial is _sentinel:
it = iter(it)
accumulator = next(it)
else:
accumulator = initial
def callback(result):
nonlocal accumulator
accumulator = result
return result
for element in it:
yield accumulator, element, callback
这是列表理解中的cofold
。
>>> [r(a+b) for a, b, r in cofold(range(10))]
[1, 3, 6, 10, 15, 21, 28, 36, 45]
元素代表对偶归约中的每一步。最后一个是我们的答案。 last
函数很简单。
def last(it):
for e in it:
pass
return e
与 reduce
不同,cofold
是惰性生成器,因此在生成器表达式中使用时可以安全地作用于无限迭代。
>>> from itertools import islice, count
>>> lazy_results = (r(a+b) for a, b, r in cofold(count()))
>>> [*islice(lazy_results, 0, 9)]
[1, 3, 6, 10, 15, 21, 28, 36, 45]
>>> next(lazy_results)
55
>>> next(lazy_results)
66
下面的python教程是这样说的:
List comprehension is a complete substitute for the lambda function as well as the functions
map()
,filter()
andreduce()
.
但是,它没有提到列表推导式如何替代 reduce()
的示例,我想不出它应该如何实现的示例。
有人可以解释一下如何通过列表理解实现类似 reduce 的功能,或者确认这是不可能的吗?
理想情况下,列表理解是创建一个新列表。引用 official documentation,
List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
而 reduce
用于将可迭代对象减少为单个值。引用 functools.reduce
,
Apply function of two arguments cumulatively to the items of sequence, from left to right, so as to reduce the sequence to a single value.
因此,列表理解不能用作 drop-in 替代 reduce
。
起初我很惊讶 Python 的创建者 Guido van Rossum 反对 reduce
。他的推理是,除了求和、乘法、and-ing 和 or-ing 之外,使用 reduce
会产生一个不可读的解决方案,该解决方案更适合迭代并更新累加器的函数。他关于此事的文章是here。所以不,没有 reduce
的列表理解替代方法,"pythonic" 方法是以老式的方式实现累积函数:
而不是:
out = reduce((lambda x,y: x*y),[1,2,3])
使用:
def prod(myList):
out = 1
for el in myList:
out *= el
return out
当然没有什么可以阻止您继续使用 reduce
(python 2) 或 functools.reduce
(python 3)
列表理解应该return 列表。如果你的 reduce 应该是 return 一个列表,那么是的,你可以用列表理解替换它。
但这并不妨碍提供"reduce-like functionality"。 Python 列表可以包含任何对象。如果您接受包含在单项列表中的结果,那么有一个 [...][0]
列表理解形式可以替换任何 reduce()
。
这应该是显而易见的,但是那个形式是
[x for x in [reduce(function, sequence, initial)]][0]
对于一些二进制 function
和一些可迭代的 sequence
和一些 initial
值。或者,如果您想要来自第一个可迭代对象的 initial
,
[x for x in [reduce(function, sequence)]][0]
可以说,以上是作弊,而且毫无意义,因为你可以在没有理解的情况下使用 reduce
。因此,让我们在没有 reduce
.
[stack.append(function(stack.pop(), e)) or stack[0]
for stack in ([initial],)
for e in sequence][-1]
这会生成所有中间值的列表,我们需要最后一个。 [-1]
和 [0]
一样简单。我们需要一个累加器来减少,但不能在理解中使用赋值语句,因此 stack
(这只是一个列表),但我们可以在这里使用许多其他数据结构。 .append()
总是 returns None
,所以我们使用 or stack[0]
将到目前为止的值放入结果列表中。
没有initial
,
[stack.append(function(stack.pop(), e)) or stack[0]
for it in [iter(sequence)]
for stack in [[next(it)]]
for e in it][-1]
真的,此时您不妨使用 for
语句。
但这会占用中间值列表的内存。对于很长的序列,这可能是个问题。但是我们也可以通过使用生成器表达式来避免这种情况。
这样做很棘手,所以让我们从一个更简单的示例开始,然后逐步完成。
stack = [initial]
[stack.append(function(stack.pop(), e)) for e in sequence]
stack.pop() # returns the answer
它会计算答案,但也会创建一个无用的 None
列表。我们可以通过将其转换为列表理解内的生成器表达式来避免这种情况。
stack = [initial]
[_ for _s in (stack.append(function(stack.pop(), e)) or ()
for e in sequence)
for _ in _s]
stack.pop()
列表理解耗尽了更新堆栈的生成器,但return它本身是一个空列表。这是可能的,因为内循环总是有零次迭代,因为 _s
总是一个空元组。
如果最后一个 _s
有一个元素,我们可以将 stack.pop()
移到里面。不过,该元素是什么并不重要。所以我们将 [None]
链接为最终的 _s
.
from itertools import chain
stack = [initial]
[stack.pop()
for _s in chain((stack.append(function(stack.pop(), e)) or ()
for e in sequence),
[[None]])
for _ in _s][0]
同样,我们有一个单项列表理解。我们还可以将 chain
实现为生成器表达式。您已经了解了如何使用单项列表将 stack
变量移入内部。
[stack.pop()
for stack in [[initial]]
for _s in (
x
for xs in [
(stack.append(function(stack.pop(), e)) or ()
for e in sequence),
[[None]],
]
for x in xs)
for _ in _s][0]
而且我们还可以从两个参数的序列中得到初始值reduce
。
[stack.pop()
for it in [iter(sequence)]
for stack in [[next(it)]]
for _s in (
x
for xs in [
(stack.append(function(stack.pop(), e)) or ()
for e in it),
[[None]],
]
for x in xs)
for _ in _s][0]
这太疯狂了。但它有效。所以是的,可能 得到 "reduce-like functionality" 的理解。这并不意味着您 应该 。七个 for
s 太难了!
你可以通过使用我命名为 last
和 cofold
:
>>> last(r(a+b) for a, b, r in cofold(range(10)))
45
这在功能上等同于
>>> reduce(lambda a, b: a+b, range(10))
45
请注意,与 reduce()
不同,理解没有使用 lambda
。
诀窍是使用带有回调的生成器 "return" 运算符的结果。 cofold
是 reduce(或折叠)函数的 corecursive dual。
_sentinel = object()
def cofold(it, initial=_sentinel):
if initial is _sentinel:
it = iter(it)
accumulator = next(it)
else:
accumulator = initial
def callback(result):
nonlocal accumulator
accumulator = result
return result
for element in it:
yield accumulator, element, callback
这是列表理解中的cofold
。
>>> [r(a+b) for a, b, r in cofold(range(10))]
[1, 3, 6, 10, 15, 21, 28, 36, 45]
元素代表对偶归约中的每一步。最后一个是我们的答案。 last
函数很简单。
def last(it):
for e in it:
pass
return e
与 reduce
不同,cofold
是惰性生成器,因此在生成器表达式中使用时可以安全地作用于无限迭代。
>>> from itertools import islice, count
>>> lazy_results = (r(a+b) for a, b, r in cofold(count()))
>>> [*islice(lazy_results, 0, 9)]
[1, 3, 6, 10, 15, 21, 28, 36, 45]
>>> next(lazy_results)
55
>>> next(lazy_results)
66