最近发现了迭代器的一个妙招,忍不住分享给爱学习的你。关于可迭代对象,迭代器,生成器的定义和理解,可以参考前文Python基础系列--可迭代对象、迭代器与生成器简而言之,它们的关系可以使用下图表示:知乎上看到这样一个图,更形象:
下面我们来做一道算法题:
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/is-subsequence
要解决这个问题,常规算法是贪心算法,维护两个指针 Ps,Pt 分别指向两个字符串 s,t 的最开始,然后对字符串 t 一路扫过去,如果某个字符和 Ps 一样,那么就把 Ps 指针前进一步,当 Ps 移出字符串 s 的最后一个字符的时候,返回 True,否则返回 False。不过,这个代码写下来怎么也得 10 行左右。如果使用迭代器和生成器,两行就搞定了:
>>> def is_subseq(s,t):
... t = iter(t)
... return all(i in t for i in s)
...
>>> is_subseq('ace','abcde')
True
>>> is_subseq('aec','abcde')
False
>>>
如果你一眼就看穿代码背后的逻辑,那说明已经是资深的 Python 工程师了,如果是一头雾水,那么我们把代码展开一步一步看:首先,将原字符串变成一个迭代器:
>>> t = 'abcde'
>>> iter(t)
<str_iterator object at 0x7fa7f22c8a60>
>>> t = iter(t)
>>> t
<str_iterator object at 0x7fa7f229d4c0>
然后遍历子字符串,依次判断每个字符是否在上述的迭代器 t 中:
(i in t for i in s)
一般的的思维定势:i 必然在 t 中,因为字串 aec 的每个字符都存在于原字符串 abcde 中。妙就妙在在这个 in 操作,in 操作判断一个字符是否在一个迭代器中,肯定是要遍历这个迭代器的,而遍历就是调用迭代器的__next__
方法,当存在时直接返回 True,而下一次判断时继续 __next__
而并不回到起始位置重新查找。这一点,我们可以一步一步执行 next 做实验:以 s= 'aec' 和 t = 'abcdef' 为例:
>>> t = 'abcdef'
>>> s = 'aec'
>>> t = iter(t)
>>> 'a' in t
True
>>> next(t)
'b'
>>> 'e' in t
True
>>> #此时指针已经指向 e,再调用 next 回返回 f
>>> next(t)
'f'
>>> 'c' in t #指针已经指向 f,f 后面不会出现字符 c,必然返回 false
False
看到这里,相信你已经明白了,in 操作返回 True 之后,下一次再 in 判断时,是从上一次结束的位置开始查找,换句话说,在循环里面用 in 来判断一个元素是否在一个迭代器中,迭代器会自动记忆上一次判断后的位置,这相当于一个隐形指针,对于解决这道问题,提供了莫大的便利。最后的 all 函数,用来判断一个迭代器的元素是否全部为 True,如果是则返回 True,否则就返回 False。Python 就是这么神奇而优雅。不过你一定注意,面试的时候尽量不要用这种技巧,因为你的面试官有可能并不知道生成器的用法,这个技术知识点上,在实际工作的应用上,你已经比很多人更加熟练了。