Python zip()用法,看这一篇就够了

介绍

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')]

很明显,对于我们的两个listablist(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)  #前面加*号,事实上*号也是一个特殊的运算符,叫解包运算符

就可以得到原来的ab了。利用这个特性,可以用一种特殊的方法处理一些问题,我们待会说。

用法

同时遍历多个字典

在 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.0051,000.0048,000.00
成本46,800.0045,900.0043,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

  • 287
    点赞
  • 1067
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值