我想仅使用高阶函数编写functional等效的列表推导,并且没有副作用。我这样做是出于严格的学习目的。 我知道列表推导是Pythonic。在Python中map(f, xs)
相当于[f(x) for x in xs]
。但是下面这些的等价物是什么?
[f(x, y) for x in xs for y in ys]
[f(x, y) for x in range(1, 5) for y in range(x, 5)]
map
仅返回相同长度的列表。 reduce
更为通用,您可以在其上实施map
和filter
。
map(f, xs) == reduce(lambda a, e: a + [f(e)], xs, [])
filter(p, xs) == reduce(lambda a, e: a + [e] if p(e) else a, xs, [])
因此A可以实现为:
def map2(f, xs, ys):
reduce(lambda a, x: a + map(lambda y: f(x, y), ys), xs, [])
但是对于子句,这并不概括为> 2。而且B更加棘手,因为1st for子句的迭代变量在第2个子句中使用。如何编写实现列表推导功能的函数(或函数集)?
答案 0 :(得分:13)
这是monad的模式,特别是列表monad。在许多语言中,monad隐藏在某种语法糖之后,例如C#的LINQ,Scala的sequence comprehensions,Haskell的do-notation,或者甚至更多的语言,(多)列表推导(像这里的Python)。
从任何这些含糖语法转换为普通函数的关键术语是(在列表的特殊情况下)类型([a], a -> [b]) -> [b]
的函数,它是monad定义的基本部分。该功能以不同的名称已知,例如(>>=)
或“绑定”,flatMap
或concatMap
或selectMany
。
对于列表的情况,concatMap
或flatMap
可能是最好的名称,因为它的作用是:在列表上映射一个返回列表的函数,给出一个列表列表;然后,压扁该列表。
现在有更具体的内容 1 :
> from functools import reduce
> from operator import add
> def concatMap(xs, f):
return reduce(add, map(f, xs), []) # only map and reduce!
测试:
> [x*y for x in range(1 ,5) for y in range(x, 5)]
> [1, 2, 3, 4, 4, 6, 8, 9, 12, 16]
> concatMap(range(1, 5), lambda x: concatMap(range(x, 5), lambda y:[x*y]))
> [1, 2, 3, 4, 4, 6, 8, 9, 12, 16]
更有趣:
> [x*y+z for x in range(1, 5) for y in range(x, 5) for z in range(x, y)]
> [3, 4, 5, 5, 6, 7, 8, 10, 11, 15]
> concatMap(range(1, 5),lambda x: concatMap(range(x, 5), lambda y: concatMap(range(x, y),lambda z: [x*y+z])))
> [3, 4, 5, 5, 6, 7, 8, 10, 11, 15]
最后,应该注意的是,尽管monad总是需要类似map
的函数,但通常reduce
是不够的 - 实际需要的是一个广义的“展平”操作{ {3}},类型为m<m<a>>
,(使用模板/泛型语法),其中m
是相关monad的类型。
1 如评论中所述,这也可以定义为concatMap = lambda xs, f: chain.from_iterable(map(f, xs))
,使用itertools
和身份(>>=) ≡ join . fmap
。
答案 1 :(得分:2)
您可以使用itertools.starmap
和itertools.product
来查看案例 A :
from itertools import product, starmap
list(starmap(f, product(xs, ys)))
<强>演示:强>
>>> from operator import mul
>>> [mul(x, y) for x in range(1, 4) for y in 'abc']
['a', 'b', 'c', 'aa', 'bb', 'cc', 'aaa', 'bbb', 'ccc']
>>> list(starmap(mul, product(range(1, 4), 'abc')))
['a', 'b', 'c', 'aa', 'bb', 'cc', 'aaa', 'bbb', 'ccc']