给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/is-subsequence

要解决这个问题,常规算法是贪心算法。我们维护两个指针指向两个字符串的开头,然后对第二个字符串一路扫过去,如果某个字符和第一个指针指的一样,那么就把第一个指针前进一步。第一个指针移出第一个序列最后一个元素的时候,返回 True,否则返回 False。

不过,这个算法正常写的话,写下来怎么也得八行左右:

def is_subsequence(s: str, t: str) -> bool:n, m = len(s), len(t)i = j = 0while i < n and j < m:if s[i] == t[j]:i += 1j += 1return i == nprint(is_subsequence("ace", "abcde"))
print(is_subsequence("aec", "abcde"))

但如果我们使用迭代器和生成器,代码量将大幅度减少:

def is_subsequence(s: str, t: str) -> bool:t = iter(t)return all(i in t for i in s)print(is_subsequence("ace", "abcde"))
print(is_subsequence("aec", "abcde"))

而且运行结果正确与上面一样,都是:

True
False

但如果你对python的生成器运行机制不太了解,一定会看的一脸懵逼。

不过不用担心,我今天分享的主题便是python的迭代器和生成器剖析

本文目录

  • 迭代器和可迭代对象

  • 列表生成式与列表生成器

  • 函数生成器(generator)

  • 迭代器和生成器的关系

  • 利用生成器判断子序列详解

  • 总结

迭代器和可迭代对象

在 Python 中一切皆对象,对象的抽象就是类,而对象的集合就是容器。

列表(list: [0, 1, 2]),元组(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。对于容器,可以认为是多个元素在一起的单元;而不同容器的区别,正是在于内部数据结构的实现方法。

所有的容器都是可迭代对象(iterable):

from collections.abc import Iterable
params = [1234,'1234',[1, 2, 3, 4],set([1, 2, 3, 4]),{1: 1, 2: 2, 3: 3, 4: 4},(1, 2, 3, 4)
]for param in params:print(f'{param}是否为可迭代对象? ', isinstance(param, Iterable))

运行结果:

1234是否为可迭代对象?  False
1234是否为可迭代对象?  True
[1, 2, 3, 4]是否为可迭代对象?  True
{1, 2, 3, 4}是否为可迭代对象?  True
{1: 1, 2: 2, 3: 3, 4: 4}是否为可迭代对象?  True
(1, 2, 3, 4)是否为可迭代对象?  True

可以看到所有的集合容器都是可迭代对象(iterable),字符串也是可迭代对象,唯有单个数字不是可迭代对象。

而可迭代对象,可以通过 iter() 函数返回一个迭代器,当然迭代器本身也属于可迭代对象:

from collections.abc import Iterable, Iterator
params = ['1234',[1, 2, 3, 4],set([1, 2, 3, 4]),{1: 1, 2: 2, 3: 3, 4: 4},(1, 2, 3, 4)
]for param in params:param = iter(param)print("----------")print(f'{param}是否为可迭代对象? ', isinstance(param, Iterable))print(f'{param}是否为迭代器对象? ', isinstance(param, Iterator))

运行结果:

这意味着迭代器本身也可以获取它自己的迭代器,例如:

for i in iter(l):print(i, end=",")
print()
for i in iter(iter(l)):print(i, end=",")

运行结果:

1,2,3,4,
1,2,3,4,

迭代器(iterator)提供了一个 __next__的方法。调用这个方法后,你要么得到这个容器的下一个对象,要么得到一个 StopIteration 的错误:

l = [1, 2, 3, 4]
l = iter(l)
while True:print(l.__next__())

结果:

1
2
3
4
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-16-e106f3a7bd73> in <module>()2 l = iter(l)3 while True:
----> 4     print(l.__next__())StopIteration:

当然上面的l.__next__()应该改写成next(l),next()方法的本质就是调用目标对象的__next__()方法。

实际上for循环:

l = [1, 2, 3, 4]
for i in l:print(i)

的本质等价于:

l = [1, 2, 3, 4]
l_iter = iter(l)
while True:try:i = next(l_iter)except StopIteration:breakprint(i)

for in 语句将这个过程隐式化了。

列表生成式与列表生成器

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

print([x * x for x in range(1, 11)])
print([x * x for x in range(1, 11) if x % 2 == 0])##还可以使用两层循环,可以生成全排列:
print([m + n for m in 'ABC' for n in 'XYZ'])
print([str(x)+str(y) for x in range(1,6) for y in range(11,16)])##for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:
d = {'x': 'A', 'y': 'B', 'z': 'C' }
print([k + '=' + v for k, v in d.items()])

结果:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[4, 16, 36, 64, 100]
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
['111', '112', '113', '114', '115', '211', '212', '213', '214', '215', '311', '312', '313', '314', '315', '411', '412', '413', '414', '415', '511', '512', '513', '514', '515']
['x=A', 'y=B', 'z=C']

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

只要把一个列表生成式的[]改成(),就创建了一个generator:

g = (x * x for x in range(10))

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

用一个示例,感受一下生成器相对生成式的优势,首先创建一个查看当前内存情况的方法:

import os
import psutil## 显示当前 python 程序占用的内存大小
def show_memory_info(hint):pid = os.getpid()p = psutil.Process(pid)info = p.memory_full_info()memory = info.uss / 1024. / 1024print(f'{hint}内存使用: {memory} MB')

测试一下列表生成式

def test_iterator():show_memory_info('initing iterator')list_1 = [i for i in range(100000000)]show_memory_info('after iterator initiated')print(sum(list_1))show_memory_info('after sum called')%time test_iterator()

结果:

initing iterator内存使用: 48.69140625 MB
after iterator initiated内存使用: 3936.2890625 MB
4999999950000000
after sum called内存使用: 3936.29296875 MB
Wall time: 9.39 s

测试一下列表生成器

def test_generator():show_memory_info('initing generator')list_2 = (i for i in range(100000000))show_memory_info('after generator initiated')print(sum(list_2))show_memory_info('after sum called')%time test_generator()

结果:

initing generator内存使用: 48.8515625 MB
after generator initiated内存使用: 48.85546875 MB
4999999950000000
after sum called内存使用: 49.11328125 MB
Wall time: 7.95 s

声明一个迭代器很简单,[i for i in range(100000000)]就可以生成一个包含一亿元素的列表。每个元素在生成后都会保存到内存中,你通过上面的代码可以看到,它们占用了巨量的内存,内存不够的话就会出现 OOM 错误。

不过,我们并不需要在内存中同时保存这么多东西,比如对元素求和,我们只需要知道每个元素在相加的那一刻是多少就行了,用完就可以扔掉了。

函数生成器(generator)

如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是用函数把它打印出来却很容易:

def fib(max):n, a, b = 0, 0, 1while n < max:print(b)a, b = b, a + bn = n + 1fib(6)

打印结果:

1
1
2
3
5
8

上面的函数和生成器(generator)仅一步之遥,只要把print(b)改为yield b,fib函数就会变成生成器(generator):

def fib(max):n, a, b = 0, 0, 1while n < max:yield ba, b = b, a + bn = n + 1

这就是除了列表生成器以外定义generator的另一种方法。

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

fib(6)

结果:

<generator object fib at 0x0000000005F04A98>

在前面的列表生成器中我已经讲过,对于生成器可以使用for循环进行遍历:

for i in fib(6):print(i)

打印结果:

1
1
2
3
5
8

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

def odd():print('step 1')yield 1print('step 2')yield(3)print('step 3')yield(5)

调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

o = odd()
while True:print(next(o))

结果:

step 1
1
step 2
3
step 3
5
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-7-554c5fb505f8> in <module>()1 o = odd()2 while True:
----> 3     print(next(o))StopIteration:

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就抛出StopIteration异常。

对于函数生成器(generator)来说,遇到return语句就是结束generator的指令(函数体最后一行语句其实隐式执行了return None),for循环随之结束。

迭代器和生成器的关系

其实生成器就是一种特殊的迭代器,而迭代器包括了生成器并不等价于生成器,它们都可以通过next()方法不断的获取下一个对象,都具备记忆已经读取的位置的特点。

例如:

l = [1, 2, 3, 4]
l_iter = iter(l)

完成可以理解为产生了一个列表生成器:

l = [1, 2, 3, 4]
l_iter = (i for i in l)

也可以理解成产生了一个函数生成器:

l = [1, 2, 3, 4]def func_generator(l):for i in l:yield il_iter = func_generator(l)

利用生成器判断子序列详解

有了前面的基础知识,相信文章开头的代码还稍微有点眉目了。现在我们再回到文章开头的代码,详细分析一下:

def is_subsequence(s: str, t: str) -> bool:t = iter(t)return all(i in t for i in s)print(is_subsequence("ace", "abcde"))
print(is_subsequence("aec", "abcde"))

首先t = iter(t)我们可以理解为产生了一个生成器:

t = (i for i in t)

i in t基本上等价于:

while True:val = next(t)if val == i:yield True

测试一下:

t = "abcde"
t = (i for i in t)
print('a' in t)
print('c' in t)
print(next(t))

结果:

True
True
d

可以看到最后一行直接返回了匹配到c的下一个值'd'。

这样我们再测试:

t = "abcde"
t = (i for i in t)
print('a' in t)
print('c' in t)
print('b' in t)

结果:

True
True
False

于是就可以顺利的使用生成器计算子序列的每个元素是否满足条件:

t = iter("abcde")
[i in t for i in "aec"]

结果:

[True, True, False]

all()函数即可判断是否全部都满足条件:

print(all([True, True, False]), all([True, True, True]))

结果:

False True

而上述代码all(i in t for i in s)没有申明all([i in t for i in s])列表生成式形式则代表是对一个列表生成器进行all运算。

总结

于是到此,我们就很优雅地解决了这道题。不过一定要注意,实际工作中尽量不要用这种技巧,因为你的领导和同事有可能并不知道生成器的用法,你即使写了详细的注释他们也难以理解,不如用常规方法解决比较好!经过今天的学习,希望你已经在生成器这个技术知识点上比其他人更加熟练了。

今天本文分享了容器、可迭代对象、迭代器和生成器四种不同的对象:

  • 容器是可迭代对象,可迭代对象调用 iter() 函数可以得到一个迭代器。

  • 迭代器可以通过 next() 函数来得到下一个元素,从而支持遍历。

  • 生成器是一种特殊的迭代器(迭代器却不见得是生成器)。


推荐阅读:
入门: 最全的零基础学Python的问题  | 零基础学了8个月的Python  | 实战项目 |学Python就是这条捷径
干货:爬取豆瓣短评,电影《后来的我们》 | 38年NBA最佳球员分析 |   从万众期待到口碑扑街!唐探3令人失望  | 笑看新倚天屠龙记 | 灯谜答题王 |用Python做个海量小姐姐素描图 |
趣味:弹球游戏  | 九宫格  | 漂亮的花 | 两百行Python《天天酷跑》游戏!
AI: 会做诗的机器人 | 给图片上色 | 预测收入 | 碟中谍这么火,我用机器学习做个迷你推荐系统电影

年度爆款文案

  • 1).卧槽!Pdf转Word用Python轻松搞定!

  • 2).学Python真香!我用100行代码做了个网站,帮人PS旅行图片,赚个鸡腿吃

  • 3).首播过亿,火爆全网,我分析了《乘风破浪的姐姐》,发现了这些秘密

  • 4).80行代码!用Python做一个哆来A梦分身

  • 5).你必须掌握的20个python代码,短小精悍,用处无穷

  • 6).30个Python奇淫技巧集

  • 7).我总结的80页《菜鸟学Python精选干货.pdf》,都是干货

  • 8).再见Python!我要学Go了!2500字深度分析!

  • 9).发现一个舔狗福利!这个Python爬虫神器太爽了,自动下载妹子图片

点阅读原文,领全套AI资料!

Python中神奇的迭代器和生成器相关推荐

  1. 完全理解 Python 迭代对象、迭代器、生成器(转)

    完全理解 Python 迭代对象.迭代器.生成器 本文源自RQ作者的一篇博文,原文是Iterables vs. Iterators vs. Generators » nvie.com,俺写的这篇文章是 ...

  2. python平方数迭代器_对python中的高效迭代器函数详解

    python中内置的库中有个itertools,可以满足我们在编程中绝大多数需要迭代的场合,当然也可以自己造轮子,但是有现成的好用的轮子不妨也学习一下,看哪个用的顺手~ 首先还是要先import一下: ...

  3. Python高级特性:迭代器和生成器

    在Python中,很多对象都是可以通过for语句来直接遍历的,例如list.string.dict等等,这些对象都可以被称为可迭代对象.至于说哪些对象是可以被迭代访问的,就要了解一下迭代器相关的知识了 ...

  4. python:容器、迭代器、生成器 简单介绍

    python:容器.迭代器.生成器 简单介绍 python提供了多种数据类型来存放数据项. 之前已经介绍了几个python中常用的容器,分别是列表list.元组tuple.字典dict和集合set. ...

  5. Python开发——函数【迭代器、生成器、三元表达式、列表解析】

    递归和迭代 小明问路篇解释说明 递归:小明-->小红-->小于-->小东:小东-->小于-->小红-->小明 小明向小红问路,因小红不知道,所以向小于问路,因小于不 ...

  6. python的装饰器迭代器与生成器_详解python中的生成器、迭代器、闭包、装饰器

    迭代是访问集合元素的一种方式.迭代器是一个可以记住遍历的位置的对象.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退. 1|1可迭代对象 以直接作用于 for ...

  7. 完全理解Python迭代对象、迭代器、生成器

    本文源自RQ作者的一篇博文,原文是Iterables vs. Iterators vs. Generators,俺写的这篇文章是按照自己的理解做的参考翻译,算不上是原文的中译版本,推荐阅读原文,谢谢网 ...

  8. 完全理解python迭代对象_完全理解Python迭代对象、迭代器、生成器

    1.assert:python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假.可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触 ...

  9. python函数知识四 迭代器、生成器

    15.迭代器:工具 1.可迭代对象: ​ 官方声明,只要具有__iter__方法的就是可迭代对象 list,dict,str,set,tuple -- 可迭代对象,使用灵活 #方法一: list.__ ...

  10. python (八)迭代器、生成器、列表推导式

    一.迭代器 1.先来讲讲什么是可迭代对象 字符串.列表.元组.字典.集合都可以被for循环,说明他们都是可迭代的. 2.怎么判断是不是一个可迭代对象 判定方法:内部含有'__iter__'方法的数据就 ...

最新文章

  1. python培训比较好的机构-西安比较好的python培训机构推荐
  2. mac 安装淘宝镜像报错之坑
  3. R语言之离群点检验(part1)--利用箱线图原理检测离群点
  4. opencv python教程简书_Python-OpenCV —— 基本操作一网打尽
  5. CF525D-Arthur and Walls【贪心】
  6. 超星考试浏览器_超星浏览器官方下载
  7. 字体管理工具字由 v2.4.0.0 绿色便携版
  8. MySQL表空间碎片
  9. A卡福利 : AMD Fluid Motion Video补帧教程,让你的视频从24帧补到60帧(144)
  10. 风光互补——三段式充电
  11. 基于HTML+CSS+JavaScript的在线图书阅读网页设计
  12. 计算机一级电子表格TF函数,TFG1000系列DDS函数信号发生器基本操作
  13. 现代 Web 开发的现状与未来
  14. react的ref三种用法
  15. java毕业2年如何做到月薪2万
  16. android移动支付——PayPal支付
  17. 毕业设计-基于深度学习的数字病理图像分割
  18. flarum 轻论坛安装教程
  19. Oracle 交、差、并
  20. 隐私保护广告行业新生态

热门文章

  1. php 网页转换成pdf文件格式,将网页HTML转换成PDF格式文件的几种办法
  2. 某网站字幕加密的wasm分析
  3. 关于2019中国移动广西分公司社会招聘互联网电视维护岗位笔试、面试经验分享
  4. JavaScript——输出100以内的质数
  5. QQ对话框、背景渐变色
  6. 【竞赛篇-国创(大创)线上报告撰写(常用套话总结)】季度报告、中期报告、结题报告怎么写,用什么格式,附件传什么比较好
  7. 宿舍物联网门锁系统之个人小程序注册
  8. 网站设计(3常用标签)
  9. 关于微信小程序上线流程的简单总结
  10. 超详细的免费下载论文方法