文章首发于知乎专栏,欢迎关注。
https://zhuanlan.zhihu.com/py...python
如下测试代码所有基于 Python3微信
工做中有时会遇到这样的需求,取出数据中前面 10% 的值,或者最后 10% 的值。数据结构
咱们能够先对这个列表进行排序,而后再进行切片操做,很轻松的解决这个问题。可是,有没有更好的方法呢?函数
heapq 模块有两个函数 nlargest() 和 nsmallest() 能够完美解决这个问题。性能
In [50]: import heapq In [51]: n = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2, 23, 45, 76] In [52]: heapq.nlargest(3, n) Out[52]: [76, 45, 42] In [53]: heapq.nsmallest(3, n) Out[53]: [-4, 1, 2]
若是是取排在前面的 10% 应该怎么作?测试
heapq.nlargest(round(len(n)/10), n)
并且,使用这两个函数还会有更好的性能,由于在底层实现里面,会先把数据进行堆排序后放入一个列表中,而后再进行后续操做。你们若是对堆数据结构感兴趣的话,能够继续进行深刻研究,因为我了解的并不深,也没办法再展开了。设计
可是也并非何时都是这两个函数效果更好,好比只取一个最大值或者最小值,那仍是 min() 或 max() 效果更好;若是要查找的元素个数已经跟集合元素个数接近时,那仍是用 sorted(items)[:N] 更好,具体状况具体分析吧。code
之前碰到这类问题时,我都会手动建立一个字典,而后以列表中元素做为 key,进而统计出 key 出现的次数,再进行比较获得出现次数最多的元素。排序
却不知 collections 中就有专门为这类问题设计的类 Counter,瞬间感受本身蠢爆了,话很少说,直接上代码。ip
In [54]: from collections import Counter In [55]: w = ['a', 'b', 'c', 'd', 'a', 'a', 'b'] In [56]: w_count = Counter(w) In [57]: w_count Out[57]: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1}) In [58]: w_count['a'] Out[58]: 3 In [59]: top = w_count.most_common(2) In [60]: top Out[60]: [('a', 3), ('b', 2)]
能够看到,Counter 返回的就是一个字典,想知道哪一个元素出现几回,直接取,是否是很方便?
并且还有 most_common 函数,简直不要太棒。
有一个列表,以下:
In [61]: a = [1, 2, 3, 4, 5, -3]
要求过滤全部负数。须要新建一个列表?直接一行代码搞定。
In [64]: [n for n in a if n > 0] Out[64]: [1, 2, 3, 4, 5]
若是要把负数替换成 0 呢?
In [67]: [n if n > 0 else 0 for n in a] Out[67]: [1, 2, 3, 4, 5, 0]
可是有时候过滤条件可能比较复杂,这时就须要借助于 filter() 函数了。
values = ['1', '2', '-3', '-', '4', 'N/A', '5'] def is_int(val): try: x = int(val) return True except ValueError: return False ivals = list(filter(is_int, values)) print(ivals) # Outputs ['1', '2', '-3', '4', '5']
有下面这个字典:
rows = [ {'address': '5412 N CLARK', 'date': '07/01/2012'}, {'address': '5148 N CLARK', 'date': '07/04/2012'}, {'address': '5800 E 58TH', 'date': '07/02/2012'}, {'address': '2122 N CLARK', 'date': '07/03/2012'}, {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 W ADDISON', 'date': '07/02/2012'}, {'address': '4801 N BROADWAY', 'date': '07/01/2012'}, {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}, ]
那么怎么对这个字典按照 date 进行分组呢?借助于 itertools.groupby() 函数能够解决这个问题,代码以下:
# Sort by the desired field first rows.sort(key=itemgetter('date')) # Iterate in groups for date, items in groupby(rows, key=itemgetter('date')): print(date) for i in items: print(' ', i)
输出结果以下:
07/01/2012 {'address': '5412 N CLARK', 'date': '07/01/2012'} {'address': '4801 N BROADWAY', 'date': '07/01/2012'} 07/02/2012 {'address': '5800 E 58TH', 'date': '07/02/2012'} {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'} {'address': '1060 W ADDISON', 'date': '07/02/2012'} 07/03/2012 {'address': '2122 N CLARK', 'date': '07/03/2012'} 07/04/2012 {'address': '5148 N CLARK', 'date': '07/04/2012'} {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
须要注意的是,groupby() 函数仅仅检查连续相同的元素,因此在分组以前,必定要先对数据,按照分组字段进行排序。若是没有排序,便得不到想要的结果。
我经常有这样的苦恼,就是有一个列表,而后经过下标来取值,取值时很认真的数所须要元素在第几个,很怕取错值。取到值后开始下面的运算。
一段时间以后,再看这段代码,感受很陌生,已经忘了带下标的值是什么了,还须要从新看一下这个列表的由来,才找到回忆。
若是能有一个名称映射到元素上就行了,直接经过名称就能够知道元素的含义。collections.namedtuple() 函数就能够解决这个问题。
In [76]: from collections import namedtuple In [77]: subscriber = namedtuple('Subscriber', ['addr', 'joined']) In [78]: sub = subscriber('jonesy@example.com', '2012-10-19') In [79]: sub Out[79]: Subscriber(addr='jonesy@example.com', joined='2012-10-19') In [80]: sub.addr Out[80]: 'jonesy@example.com' In [81]: sub.joined Out[81]: '2012-10-19'
这样就能够经过名称来取值了,代码可读性也更高。
须要注意的是,这种命名元祖的方式不能直接修改其中的值,直接修改会报错
In [82]: a = namedtuple('SSS', ['name', 'shares', 'price']) In [83]: _a = a('yongxinz', 1, 2) In [84]: _a.shares = 4 ----------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-84-f62a5288a29a> in <module>() > 1 _a.shares = 4 AttributeError: can't set attribute
想要修改的话可使用 _replace() 函数。
In [85]: _a._replace(shares=4) Out[85]: SSS(name='yongxinz', shares=4, price=2)
可是还有一个疑问,若是这个列表元素比较多的话,那就须要定义不少的名称,也比较麻烦,还有更好的方式吗?
未完待续。。。
欢迎留言,或添加我我的微信 zhangyx6a 交流,不是微商。