关于python:Python函数式编程系列009惰性列表之常规列表

3次阅读

共计 2170 个字符,预计需要花费 6 分钟才能阅读完成。

咱们在惰性求值中,咱们介绍了「惰性列表」的概念,这个概念,其实在 Python 种也有局部原生反对。这就是很受老手困扰的 生成器 迭代器 了。但之前,咱们首先要回顾一下对于列表的性能。

从二元元组到列表

首先,咱们能够用 \(\lambda\)演算定义一个二元的元组,或者叫pair:

  1. pair: \(\lambda a b f.f a b\)
  2. first: \(\lambda p. p(\lambda a b. a)\)
  3. second: \(\lambda p. p(\lambda a b.b)\)

具体实现如下:

pair = lambda a: lambda b: lambda f: f(a)(b)
first = lambda p: p(lambda a: lambda b: a)
second = lambda p: p(lambda a: lambda b: b)

咱们能够定义测试一下:

>>> p = pair(1)(2)
>>> first(p)
1
>>> second(p)
2

当然有了 pair,定义一个列表就不是难事,即上面的形式组合就好(咱们还是用python 自带的元组示意):

(1, (2, 3, (4, ()))))

咱们将在前面的章节里别离用 元组 / 类型 的形式来定义列表。但在这篇文章里,咱们先回到之前 python 的自带的概念来,看函数式编程如何解决遍历问题的。

列表操作

列表操作,是函数式编程的一个重要概念,实时上它是通过递归来实现对一个线性后果的遍历。比方上面的类 C 格调的代码:

ls = [1, 2, 3, 4]

for i in range(0, len(ls)):
    ls[i] = ls[i] + 1

这里呈现了两个副作用,一个是 i 的自增,另一个是对 ls 的原地操作。而且,它们也用到了 变量 的概念。当然,这种写法其实无可非议,可维护性也尚可,算是能够容忍的副作用。当然咱们最简略的实现,相当于大家都晓得是列表表达式(当然,事实上它还是有副作用的):

[i + 1 for i in ls]

当然,大部分人也见过列表表达式的残缺操作,能够自带筛选:

[i + 1 for in in ls if i % 2 == 0]

这就是函数式编程遍历数据最简略的操作,当然,它们还有一个名字,就是 mapfilter,在 Python 中,它们返回的就是可迭代对象(咱们能够调用 list 转换成 列表):

map(lambda x: x + 1, ls) # [i + 1 for i in ls]
filter(lambda x: x % 2 == 0, ls) # [i for i in ls if x % 2 == 0]

另一个罕用的列表操作是 reduce,它起到的是聚合作用,咱们只有定义一个 二元运算,就能够将列表从头合并到尾聚合操作。

reduce操作视图解决的问题就是遍历后汇总值的过程。譬如,咱们要实现 ls 的求和,在个别的过程式编程中,咱们会应用如下的办法:

res = 0

for i in ls:
    res += i # 或者和上面更相似的写法 res = res + i

而,应用reduce,咱们仅须要如下代码即可实现。

from functools import reduce

reduce(lambda x: x + y, ls)

具体的计算过程如下:

  1. 获取 ls 第一个值 1 和第二个值2,套用lambda x, y: x + y,失去3
  2. 获取 ls 第三个值 3,套用第一步的后果3lambda失去6
  3. 获取 ls 第三个值 4,套用第二步的后果6lambda失去10
  4. 实现计算返回后果。

但,其实如果查看 Pythonreduce函数的参数,咱们会发现它还能够带入初始值,当带入初始值时,在各类函数式语句中,个别把它叫做 fold_left/foldl 函数。这个有没有初始值成果会不一样很多。第一个就是解决列表是空的问题:

reduce(lambda x, y: x + y, []) # 报错
reduce(lambda x, y: x + y, [], 0) # return 0

咱们甚至能够把这个和后面的过程式编程的各种元素对应起来,0相当于 res = 0lambda x, y: x + y 表白的就是 res = res + i。然而,其实foldlreduce更弱小的层面,在于,这个运算自身能够波及不同类型。咱们采纳类型标记,就会发现 reduce 函数自身的运算只能是Callable[[S, S], S]/(S, S) -> S,但其实咱们在很多场景中,须要的是一个类型装换。比方:

  1. [1, 2, 3, 4] => "1234"
  2. [1, 2, 3, 4] => [[1], [2], [3], [4]]

如果单纯应用 reduce 咱们无奈操作这种波及类型转换的内容,foldl带入的二元运算类型标注则是Callable[[S, T], S]/(S, T) -> S。这就让咱们能够通过设定一个另一个类型的初始值,来实现这件事,比方下面转换成字符串的例子,咱们很容易找到上面的二元运算(留神前后程序):

lambda x, y: x + str(y)

而初始值仅需设定一个空的 "" 字符串即可,即如下实现(尝试本人实现一下 [1, 2, 3, 4] => [[1], [2], [3], [4]] 吧!):

reduce(lambda x, y: x + str(y), ls, "")

总结

本篇文章中,咱们回顾了 Python 原生的列表,以及介绍函数式编程通过列表表达式 / 列表操作来实现过程式中常见的数据遍历的问题来躲避 for/while 中不可避免的副作用。咱们接下来将会应用 pair 的概念从头实现一个列表,而后咱们就进入到正式的惰性列表的概念中,看看惰性列表如何解决这类问题,以及用函数式思考流式解决、线程的概念。

正文完
 0