zip()
是Python的一个内建函数,在官方的文档中,它的描述是这样的:
Make an iterator that aggregates elements from each of the iterables.
Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted. With a single iterable argument, it returns an iterator of 1-tuples. With no arguments, it returns an empty iterator.
这么描述显然有些抽象,让我们直接观察一下它的运行结果:
>>> a = ['a', 'b', 'c', 'd']
>>> b = ['1', '2', '3', '4']
>>> list(zip(a, b))
[('a', '1'), ('b', '2'), ('c', '3'), ('d', '4')]
很明显,对于我们的两个list
,a
和b
,list(zip(a, b))
生成了一个列表。在这个列表中,每个元素是一个tuple
;对于第i
个元组,它其中的内容是(a[i-1], b[i-1])
。这样的操作,与压缩软件的“压缩”十分接近。如果我们继续在zip()
中加入更多的参数,比如zip(a, b, c, d)
,那么在将它转换成list
之后,结果当然就是[(a[0], b[0], c[0], d[0]), (a[1], b[1], c[1], d[1]), ..., (a[n-1], b[n-1], c[n-1], d[n-1])]
。
事实上,在 Python 3 中,为了节省空间,zip()
返回的是一个tuple
的迭代器,这也是我们为什么要调用list()
将它强制转换成list
的原因。不过,Python 2中,它直接返回的就是一个列表了。
看了上面这些介绍,你也许对zip()
这个函数有了稍微初步的认识。但是,你有没有意识到,什么地方可能存在问题呢?
这个问题就是:如果我们传入zip()
中的几个参数不等长,会有什么结果呢?zip()
很灵活,如果几个参数不等长,那么它会取最短的那个参数的长度,然后再进行处理。至于那些超出最短长度的成员,就只好被舍弃掉了。
那么,如果我们想从得到的“压缩”后的结果,还原成“压缩”前的几个列表,应该怎么做呢?
对于zip(args)
这个函数,Python还提供了一种逆操作。例如,我们有
result = zip(a, b)
那么,只要调用
origin = zip(*result) #前面加*号,事实上*号也是一个特殊的运算符,叫解包运算符
就可以得到原来的a
和b
了。利用这个特性,可以用一种特殊的方法处理一些问题,我们待会说。
在 Python 3.6+ 中,字典成为了一种有顺序的集合。利用这个特性和zip
,我们可以同时遍历多个字典:
>>> dict_one = {
'name': 'John', 'last_name': 'Doe', 'job': 'Python Consultant'}
>>> dict_two = {
'name': 'Jane', 'last_name': 'Doe', 'job': 'Community Manager'}
>>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
... print(k1, '->', v1)
... print(k2, '->', v2)
...
name -> John
name -> Jane
last_name -> Doe
last_name -> Doe
job -> Python Consultant
job -> Community Manager
考虑一个场景:你正在处理一些学生的成绩,有这样两个列表:
names = ['John', 'Amy', 'Jack']
scores = [98, 100, 85] # 分数和名字是一一对应的
如果你想对它们进行排序,又不想破坏对应关系的话,就可以这样:
data = list(zip(names, scores))
data.sort()
结果是:
[('Amy', 100), ('Jack', 85), ('John', 98)]
如果要先对分数进行排序:
data2 = list(zip(scores, names))
data2.sort()
结果是:
[(85, 'Jack'), (98, 'John'), (100, 'Amy')]
假设你有这样一个表格:
一月 | 二月 | 三月 | |
---|---|---|---|
销售额 | 52,000.00 | 51,000.00 | 48,000.00 |
成本 | 46,800.00 | 45,900.00 | 43,200.00 |
利润 = 销售额 - 成本,如果要求每月的利润的话,就可以:
total_sales = [52000.00, 51000.00, 48000.00]
prod_cost = [46800.00, 45900.00, 43200.00]
for sales, costs in zip(total_sales, prod_cost):
profit = sales - costs
print(f'Total profit: {profit}')
结果是:
Total profit: 5200.0
Total profit: 5100.0
Total profit: 4800.0
回到上面处理学生成绩的场景:如果你想将两个列表合并起来,得到一个姓名-分数的字典,就可以:
stu = dict(zip(names, scores))
结果是:
{
'John': 98, 'Amy': 100, 'Jack': 85}
还记得吗,我上面留了个坑,现在就是填坑的时候了。
利用zip()
解压的特性,我们可以使用它秒杀某些字符串处理的题目。例如,力扣的最长公共前缀:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:所有输入只包含小写字母 a-z 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-prefix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题,我一开始的思路是:
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ""
target = strs[0] # 令第一个字符串作为基准
for i in strs[1:]: # 分别对每个字符串进行比较
target = target[:min(len(target), len(i))] # 最长公共前缀肯定<=最短字符串的长度
for j in range(len(target)): # 比较基准字符串和当前字符串的没个字幕
if target[j] != i[j]: # 如果出现不一样的字符,说明最长前缀肯定不包括当前字符
if j == 0:
return ""
else:
target = target[:j] # 当前字符以前的字符都有可能是公共前缀
break
return target
最后虽然过了,但是执行用时大概排在50%左右,不是很好的成绩。当我看到最短用时的范例的时候,有一点惊为天人的感觉:
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
result = ""
for temp in zip(*strs):
if len(set(temp)) == 1:
result += temp[0]
else:
break
return result
这里就用到了解压的特性。上面已经说过,如果我们把元素压缩后,得到:
[('a', '1'), ('b', '2'), ('c', '3'), ('d', '4')]
那么还原回去就是:
['a', 'b', 'c', 'd']
['1', '2', '3', '4']
这个题目中给的字符串列表是这样的:
["flower","flow","flight"]
如果我们把它看作是压缩后的结果,也就是说,
(a[0], b[0], c[0], d[0], e[0], f[0]) == ('f', 'l', 'o', 'w', 'e', 'r')
(a[1], b[1], c[1], d[1]) == ('f', 'l', 'o', 'w')
(a[2], b[2], c[2], d[2], e[2], f[2]) == ('f', 'l', 'i', 'g', 'h', 't')
那么,如果我们将它们进行解压,得到的结果就应该是这样的:
a = ['f', 'f', 'f']
b = ['l', 'l', 'l']
c = ['o', 'o', 'i']
d = ['w', 'w', 'g']
# 没有e和f,因为压缩是按最短来的,解压也一样,上面只是为了方便说明
是不是一目了然了?
另外,力扣的螺旋矩阵也可以用到zip()
来简化代码,试试看!
Using the Python zip() Function for Parallel Iteration