Python函数式编程系列012:惰性列表之生成器与迭代器

因为本系列还是基于一些已经对Python有一定熟悉度的读者,所以我们在此不做非常多的赘述来介绍基本知识了。而是回我们之前的主题,我们要用迭代器和生成器实现之前的指数函数。

当然,我们这里还是需要回到惰性列表是什么这个问题。事实上,回到原来惰性求值的概念,惰性列表的概念其实是「需要时才计算出值」的列表。我们在调用iter的时候,其实对常见的对象并没有特别大的优势。我们可以假想,其实iter转化[1, 2, 3, 4]的结果其实如下:

def yield_list():
    yield 1
    yield 2
    yield 3
    yield 4

唯一的优势,我们之前已经提到过了,就是反复套用函数fg时,我们是计算g(f(x))而不是先把列表里每个值套用f再套用g。这里有个极大的优势,就是提前终止时可以避免没有必要的运算。比如,下面一个for里面的例子,我们是为了发现列表ls中应用f函数后如果结果等于a就返回index否则返回None

def find_index_apply_f(f, ls, a):
    for i, x in enumerate(ls):
        if f(x) == a:
            return i
        else:
            continue
    return None

>>> find_index_apply_f(lambda x: x + 1, [1, 2, 3, 4, 5], 3)
1

现在,这里提前跳出可以减少非常多的运算量,但是如果使用一个普通列表却很难,我们在使用map之后必然已经全都计算了,但如果惰性求值,我们可以就在需要的时候停止就行。这个是列表操作替代循环必须实现的东西。

第二个惰性列表的最大应用,就是无穷列表,比如下面一个生成器,我们可以生成一个无限长度的全是x的列表。后面我们会聊到我们在各种场合中已经用到了这个抽象。

def yield_x_forever(x):
    while True:
        yield x

实现一些常用的(惰性)列表操作

大部分操作迭代器/生成器的函数,我们都可以在itertoools中找到。但,我们这里还是要实现一些非常函数式的函数,方便以后的操作:

1. head

head很简单,即取出(惰性)列表第一个元素:

head = next

2. take

take的目标是列表前N个值,这个可以实现成触发计算(转化成非惰性对象,一般为一个值或者列表)或者不触发计算的版本。下面我们实现的是触发计算的函数。

def take(n, it):
    """将前n个元素固定转为列表
    """
    return [x for x in islice(it, n)]

take_curry = lambda n: lambda it: take(n, it)

3. drop

drop则相反是删去前N个值。

def drop(n, it):
    """剔除前n个元素
    """
    return islice(it, n, None)

4. tail

tail是删去head后的列表,可以用drop实现:

from functools import partial

tail = partial(drop, 1)

5. iterate

iterate是重点要用到的函数,就是通过一个迭代函数还有初始值,实现一个无穷列表:

def iterate(f, x):
    yield x
    yield from iterate(f, f(x))

比如,实现所有正偶数的无穷列表:

positive_even_number = iterate(lambda x: x + 2, 2)

当然,更简单地写法是使用itertools里面的repeataccumulate

def iterate(f, x):
    return accumulate(repeat(x), lambda fx, _: f(fx))

简单实践

例子一:求指数

我们回到之前求指数的例子中,我们可以实现惰性列表的版本。

第一个思路,我们就是直接用iteratex开始,每次乘以x,然后取出前n个值,拿到最后一个:

power = lambda x, n: take(n, iterate(lambda xx: xx * x, x))[-1]

另一个就是先生成一个无穷长度的x,取出前n个,相乘来reduce

power = lambda x, n: reduce(
    lambda x, y: x * y, 
    take(n, iterate(lambda _: x, x))
)

当然,我们还可以用生成器生成无穷长列表:

def yield_power(x, init=x):
    yield init
    yield from yield_power(x, init * x)

例子二:查找

我们回到上面解说的例子,我们要找到一个无穷列表中套用f后,第一个等于a的值的index。如果不是惰性的话,这个必须提前跳出也不可能实现。

def find_a_in_lazylist(f, lls, a):
    return head(filter(lambda x: f(x[1]) == a, enumerat(lls)))[0]

总结

本章回顾了利用Python自带的生成器、迭代器实现惰性列表,并展示如何运用这些概念做一些数据操作应用。当然在其中,我们要深刻感受到,函数式编程与数据是非常亲近的,它关注数据胜于项目结构,这点和对象式编程非常不同。大部分对象式编程的教程倾向于概述分层、结构这些概念,真是因为这个是对象式编程擅长的地方。

在我实现的教学项目fppy(点击这里前往github)中,我用内置的python模块实现了一个LazyList类,用它可以用链式写法完成上面的所有例子:

power1 = lambda x, n: LazyList.from_iter(x)(lambda xx: x * x).take(n).last
power2 = lambda x, n: LazyList.from_iter(x)(lambda _: x).take(n).reduce(lambda xx, yy: xx * yy)

find_a_in_lazylist = lambda f, lls, a: LazyList(lls)\
    .zip_with(LazyList.from_iter(0)(lambda x: x + 1))\
    .filter(lambda x: f(x[1]) == a)\
    .split_head()[0]

λ and τ
介绍关于数据可视化的方方面面,不光技术,还有哲学、文化、传播学。
1.2k 声望
101 粉丝
0 条评论
推荐阅读
最好用的 python 库合集
🎈 分词 - jieba优秀的中文分词库,依靠中文词库,利用词库确定汉子之间关联的概率,形成分词结果 {代码...} 🎈 词云库 - wordcloud对数据中出现频率较高的 关键词 生成的一幅图像,予以视觉上的突出 {代码...} 🎈 ...

tiny极客11阅读 2.8k评论 2

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.3k

滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许3阅读 1.5k

搭个ChatGPT算法模型,从哪开始?
最近 ChatGPT 很火,火到了各行各业。记得去年更多的还是码农最新体验后拿它搜代码,现在各行各业都进来体验,问它咋理财、怎么写报告和给小孩起名。😂 也因此让小傅哥在头条的一篇关于 ChatGPT 的文章都有了26万...

小傅哥6阅读 1.2k

封面图
程序员适合创业吗?
大家好,我是良许。从去年 12 月开始,我已经在视频号、抖音等主流视频平台上连续更新视频到现在,并得到了不错的评价。每个视频都花了很多时间精力用心制作,欢迎大家关注哦~考虑到有些小伙伴没有看过我的视频,...

良许3阅读 1.3k

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 4.5k评论 1

PyCharm 激活破解教程, 2023 年 2 月亲测有用
本文分享一下PyCharm 2022.2.3 版本最新激活破解教程,注意不要使用太新的版本,都是 Jetbrains 产品,本文专门配上了 Pycharm 的图片,跟着下面教程一步一步来即可。

程序员徐公阅读 8.8k评论 1

1.2k 声望
101 粉丝
宣传栏