社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

一道题让你从此真正理解Python编程

马哥Linux运维 • 3 年前 • 429 次点击  

写下这个题目的时候,脑海里无法抑制地响起了周华健那略带沙哑的歌声:

远处传来那首熟悉的歌,
那些心声为何那样微弱。
很久不见,你现在都还好吗?
有没有那么一首歌,
会让你轻轻跟着和,
随着我们生命起伏,
一起唱的主题歌;
有没有那么一首歌,
会让你突然想起我,
让你欢喜也让你忧,
这么一个我……

音乐结束,回到正题。近日浏览LeetCode,发现了一道很有意思的小题目。当我尝试用Python解答的时候,居然动用了集合、map函数、zip函数、lambda函数、sorted函数,调试过程还涉及到了迭代器、生成器、列表推导式的概念。一个看似极为简单的题目,尽管最终的代码可以合并成一行,却几乎把Python的编程技巧用了一遍,真可谓“细微之处见精神”!通过这个题目,也许会让你从此真正理解了Python编程。


这道题,名为《列表中的幸运数》。什么是幸运数呢?在整数列表中,如果一个数字的出现频次和它的数值大小相等,我们就称这个数字为「幸运数」。例如,在列表[1, 2, 2, 3]中,数字1和数字2出现的次数分别是1和2,所以它们是幸运数,但3只出现过1次,3不是幸运数。


明白了幸运数的概念,我们就来试着找出列表[3, 5, 2, 7, 3, 1, 2 ,4, 8, 9, 3]中的幸运数吧。这个过程可以分为以下几个步骤:


  • 找出列表中不重复的数字

  • 统计每个数字在列表中出现的次数

  • 找出出现次数等于数字本身的那些数字


第1步,找出列表中不重复的数字


找出列表中不重复的数字,也就是去除列表中的重复元素,简称“去重”。去重最简洁的方法是使用集合。

>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> unique = set(arr)>>> unique{1, 2, 3, 4, 5, 7, 8, 9}


第2步,统计每个数字在列表中出现的次数


我们知道,列表对象自带一个count()方法,能返回某个元素在列表中出现的次数,具体用法如下:

>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> arr.count(8) # 元素8在数组arr中出现过2次2

接下来,我们只需要遍历去重后的各个元素,逐一统计它们各自出现的次数,并保存成一个合适的数据结构,这一步工作就万事大吉了。



    
>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> unique = set(arr) # 去除重复元素>>> pairs = list() # 空列表,用于保存数组元素和出现次数组成的元组>>> for i in unique:    pairs.append((i, arr.count(i)))
>>> pairs[(1, 1), (2, 2), (3, 3), (4, 1), (5, 1), (7, 1), (8, 2), (9, 1)]

作为新手,代码写成这样,已经很不错了。但是,一个有追求的程序员绝对不会就此自满、裹足不前。他们最喜欢做的事情就是想尽千方百计消灭for循环,比如使用映射函数、过滤函数取代for循环;即便不能拒绝for循环,他们也会尽可能把循环藏起来,比如藏在列表推导式内。这里既然是要对每一个元素都调用列表的count()这个方法,那就最适合用map函数取代for循环了。

>>> m = map(arr.count, unique)>>> m>>> list(m) # 生成器可以转成列表[1, 2, 3, 1, 1, 1, 2, 1]>>> list(m) # 生成器只能用一次,用过之后,就自动清理了[]

map函数返回的是一个生成器(generator),可以像列表一样遍历,但无法像列表那样直观地看到各个元素,除非我们用list()把这个生成器转成列表(实际上并不需要将生成器转为列表)。请注意,生成器和迭代器不同,或者说生成器是一种特殊的迭代器,只能被遍历一次,遍历结束,就自动消失了。迭代器则可以反复遍历。比如,range()函数返回的就是迭代器:

>>> a = range(5)>>> list(a)[0, 1, 2, 3, 4]>>> list(a)[0, 1, 2, 3, 4]

说完生成器和迭代器,咱们还得回到原来的话题上。使用map映射函数,我们得到了每个元素的出现次数,还需要和对应的元素组成一个一个的元组。这时候,就用上zip()函数了。zip() 函数创建一个生成器,用来聚合每个可迭代对象(迭代器、生成器、列表、元组、集合、字符串等)的元素,元素按照相同下标聚合,长度不同则忽略大于最短迭代对象长度的元素。

>>> m = map(arr.count, unique)>>> z = zip(unique, m)>>> z>>> list(z)[(1, 1), (2, 2), (3, 3), (4, 1), (5, 1), (7, 1), (8, 2), (9, 1)]>>> list(z)


    
[]

很显然,zip()函数返回的也是生成器,只能用一次,过后即消失。


第3步,找出出现次数等于数字本身的那些数字


有了每个元素及其出现的次数,我们只需要循环遍历……不,请稍等,我们为什么一定要循环呢?我们只是要把每个元素过滤一遍,找出那些出现次数等于元素自身的那些元组,为什么不试试过滤函数filter()呢?

>>> def func(x): # 参数x是元组类型    if x[0] == x[1]:      return x
>>> m = map(arr.count, unique)>>> z = zip(unique, m)>>> f = filter(func, z)>>> f>>> list(f)[(1, 1), (2, 2), (3, 3)]>>> list(f)[]

过滤函数filter()接受两个参数,第1个参数是个函数,用于判断一个元素是否符合过滤条件,第2个参数就是需要过滤的可迭代对象了。filter()函数返回的也是生成器,只能用一次,过后即消失。


写这里,我们几乎要大功告成了。但是,作为一个有追求的程序员,你能容忍func()这样一个看起来怪怪的函数吗?答案是不能!你一定会用lambda函数取代它。另外,也许我们还需要对结果按照元素的大小排序。加上排序,完整代码如下:

>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> unique = set(arr)>>> m = map(arr.count, unique)>>> z = zip(unique, m)>>> f = filter(lambda x:x[0]==x[1], z)>>> s = sorted(f, key=lambda x:x[0])>>> print('幸运数是:', [item[0] for item in s])幸运数是:[1, 2, 3]


终极代码,一行搞定


如果你曾经有过被那些写成一行、却能实现复杂功能的、看起来像天书一样的代码蹂躏的痛苦经历,那么,现在你也可以把上面的代码写成一行,去蹂躏别人了。




    
>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> print('幸运数是:', [item[0] for item in sorted(filter(lambda x:x[0]==x[1], zip(set(arr), map(arr.count, set(arr)))), key=lambda x:x[0])])幸运数是:[1, 2, 3]


戏剧性反转,这次真的理解Python了!


有人说,何必那么麻烦呢?这样写不是更简单、更易读吗?果然,我真是想多了!

>>> arr = [3,5,2,7,3,8,1,2,4,8,9,3]>>> [x for x in set(arr) if x == arr.count(x)][1, 2, 3]

原文链接:https://blog.csdn.net/xufive/article/details/105215593

文章转载:Python编程学习圈
(版权归原作者所有,侵删)

点击下方“阅读原文”查看更多

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/126332
 
429 次点击