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

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

音乐结束,回到正题。近日浏览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



<map object at 0x0000020A2D090E08>



>>> 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



<zip object at 0x0000020A2D490508>



>>> 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



<filter object at 0x0000020A2D1DD908>



>>> 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)]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,888评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,677评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,386评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,726评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,729评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,337评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,902评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,807评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,349评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,439评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,567评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,242评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,933评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,420评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,531评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,995评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,585评论 2 359

推荐阅读更多精彩内容