Python Counter() 的实现
Table of Contents
collections.Counter 源码实现
Counter
的相关源码在lib下的collections.py里,本文所提及的源码是python2.7版本, 可参见github。
__init__
class Counter(dict):
'''Dict subclass for counting hashable items. Sometimes called a bag
or multiset. Elements are stored as dictionary keys and their counts
are stored as dictionary values.
'''
def __init__(*args, **kwds):
'''Create a new, empty Counter object. And if given, count elements
from an input iterable. Or, initialize the count from another mapping
of elements to their counts.
>>> c = Counter() # a new, empty counter
>>> c = Counter('gallahad') # a new counter from an iterable
>>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
>>> c = Counter(a=4, b=2) # a new counter from keyword args
'''
if not args:
raise TypeError("descriptor '__init__' of 'Counter' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
super(Counter, self).__init__()
self.update(*args, **kwds)
Counter
继承字典类来实现,初始化中对参数进行有效性校验,其中 args
接受除了 self
外最多一个未知参数。校验完成后调用自身的 update
方法来具体创建数据结构。
update
def update(*args, **kwds):
'''Like dict.update() but add counts instead of replacing them.
'''
if not args:
raise TypeError("descriptor 'update' of 'Counter' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None:
if isinstance(iterable, Mapping):
if self:
self_get = self.get
for elem, count in iterable.iteritems():
self[elem] = self_get(elem, 0) + count
else:
super(Counter, self).update(iterable) # fast path when counter is empty
else:
self_get = self.get
for elem in iterable:
self[elem] = self_get(elem, 0) + 1
if kwds:
self.update(kwds)
update
方法先检查参数,位置参数除了self外只允许有一个。然后对传入的参数进行判断,如果是以 Counter(a=1,b=2)
的方式调用的,这时候取出 kwds({'a':1,'b'=2})
再调用自身,将关键字参数转化为位置参数处理。
如果传入的位置参数是一个mapping类型的,对应于 Counter({'a':1,'b':2})
这样的方式调用,这种情况会判断self是否为空,在初始化状态下self总是空的,这边加上判断是因为update 方法不仅近在 __init__()
里调用,还可以这样调用:
x1 = collections.Counter({'a': 1, 'b': 2})
x2 = collections.Counter(a=1, b=2)
x1.update(x2) # Counter()类型 isinstance(iterable, Mapping) 也返回 True
# 或者这样调用
x1 = collections.Counter({'a': 1, 'b': 2})
x1.update('aab')
如果传入的不是一个mapping类型,那么会迭代该参数的每一项作为key添加到Counter中
most_common
def most_common(self, n=None):
'''List the n most common elements and their counts from the most
common to the least. If n is None, then list all element counts.
>>> Counter('abcdeabcdabcaba').most_common(3)
[('a', 5), ('b', 4), ('c', 3)]
'''
# Emulate Bag.sortedByCount from Smalltalk
if n is None:
return sorted(self.iteritems(), key=_itemgetter(1), reverse=True)
return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
如果调用 most_common
不指定参数n则默认返回全部(key, value)组成的列表,按照value降序排列。
itemgetter
这里用到了有趣的 itemgetter(代码里用了别名_itemgetter)
, 它是来自 operator
模块中的方法,可以从下面的代码感受一下:
# 例子来源python文档
# 举例:
After f = itemgetter(1), the call f(r) returns r[1].
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).
# 实现:
def itemgetter(*items):
if len(items) == 1:
item = items[0]
def g(obj):
return obj[item]
else:
def g(obj):
return tuple(obj[item] for item in items)
return g
# 常见用法:
>>> itemgetter(1)('ABCDEFG')
'B'
>>> itemgetter(1,3,5)('ABCDEFG')
('B', 'D', 'F')
>>> itemgetter(slice(2,None))('ABCDEFG')
'CDEFG'
>>> inventory = [('apple', 3), ('banana', 2), ('pear', 5), ('orange', 1)]
>>> getcount = itemgetter(1)
>>> map(getcount, inventory)
[3, 2, 5, 1]
>>> sorted(inventory, key=getcount)
[('orange', 1), ('banana', 2), ('apple', 3), ('pear', 5)]
heapquue
heap queue是“queue algorithm”算法的python实现,调用 _heapq.nlargest()
返回了根据每个value排序前n个大的(key, value)元组组成的列表。具体heap queue使用参见文档。
elements
elements
方法实现了按照value的数值重复返回key。它的实现很精妙,只有一行:
def elements(self):
'''Iterator over elements repeating each as many times as its count.
>>> c = Counter('ABCABC')
>>> sorted(c.elements())
['A', 'A', 'B', 'B', 'C', 'C']
'''
return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
该实现里用到了 itertools
里的 repeat
starmap
chain
三个方法, 直接按照每项计数的次数重复返回每项内容,拼成一个列表。
repeat
repeat生成一个迭代器,根据第二个参数不停滴返回接受的第一个参数。直接看实现,很好理解, 类似实现如下:
def repeat(object, times=None):
# repeat(10, 3) --> 10 10 10
if times is None:
while True:
yield object
else:
for i in xrange(times):
yield object
starmap
starmap接受的第一个参数是一个函数,生成一个迭代器,不停滴将该函数以第二个参数传来的每一项为参数进行调用(说得抽象,看例子好理解),类似实现如下:
def starmap(function, iterable):
# starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000
for args in iterable:
yield function(*args)
chain.from_iterable
chain.from_iterable 接受一个可迭代对象,返回一个迭代器,不停滴返回可迭代对象的每一项,类似实现如下:
def from_iterable(iterables):
# chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
for it in iterables:
for element in it:
yield element
substract
substract的实现和update实现很像,不同之处在counter()相同的项的计数相加改成了相减。
def subtract(*args, **kwds):
'''Like dict.update() but subtracts counts instead of replacing them.
Counts can be reduced below zero. Both the inputs and outputs are
allowed to contain zero and negative counts.
Source can be an iterable, a dictionary, or another Counter instance.
>>> c = Counter('which')
>>> c.subtract('witch') # subtract elements from another iterable
>>> c.subtract(Counter('watch')) # subtract elements from another counter
>>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch
0
>>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
-1
'''
if not args:
raise TypeError("descriptor 'subtract' of 'Counter' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None:
self_get = self.get
if isinstance(iterable, Mapping):
for elem, count in iterable.items():
self[elem] = self_get(elem, 0) - count
else:
for elem in iterable:
self[elem] = self_get(elem, 0) - 1
if kwds:
self.subtract(kwds)
**
+, -, &, |
通过对 __add__
, __sub__
, __or__
, __and__
的定义,重写了 +, -, &, |
,实现了Counter间类似于集合的操作, 代码不难理解,值得注意的是,将非正的结果略去了:
def __add__(self, other):
'''Add counts from two counters.
>>> Counter('abbb') + Counter('bcc')
Counter({'b': 4, 'c': 2, 'a': 1})
'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
for elem, count in self.items():
newcount = count + other[elem]
if newcount > 0:
result[elem] = newcount
for elem, count in other.items():
if elem not in self and count > 0:
result[elem] = count
return result
def __sub__(self, other):
''' Subtract count, but keep only results with positive counts.
>>> Counter('abbbc') - Counter('bccd')
Counter({'b': 2, 'a': 1})
'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
for elem, count in self.items():
newcount = count - other[elem]
if newcount > 0:
result[elem] = newcount
for elem, count in other.items():
if elem not in self and count < 0:
result[elem] = 0 - count
return result
def __or__(self, other):
'''Union is the maximum of value in either of the input counters.
>>> Counter('abbb') | Counter('bcc')
Counter({'b': 3, 'c': 2, 'a': 1})
'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
for elem, count in self.items():
other_count = other[elem]
newcount = other_count if count < other_count else count
if newcount > 0:
result[elem] = newcount
for elem, count in other.items():
if elem not in self and count > 0:
result[elem] = count
return result
def __and__(self, other):
''' Intersection is the minimum of corresponding counts.
>>> Counter('abbb') & Counter('bcc')
Counter({'b': 1})
'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
for elem, count in self.items():
other_count = other[elem]
newcount = count if count < other_count else other_count
if newcount > 0:
result[elem] = newcount
return result
其它
# 当用Pickler序列化时,遇到不知道怎么序列化时,查找__reduce__方法
def __reduce__(self):
return self.__class__, (dict(self),)
# 重写删除方法,当Counter有这个key再删除,避免KeyError
def __delitem__(self, elem):
'Like dict.__delitem__() but does not raise KeyError for missing values.'
if elem in self:
super(Counter, self).__delitem__(elem)
# %s : String (converts any Python object using str()).
# %r : String (converts any Python object using repr()).
def __repr__(self):
if not self:
return '%s()' % self.__class__.__name__
items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
return '%s({%s})' % (self.__class__.__name__, items)
@classmethod
def fromkeys(cls, iterable, v=None):
# There is no equivalent method for counters because setting v=1
# means that no element can have a count greater than one.
raise NotImplementedError(
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
# 实现__missing__方法,当Couter['no_field'] => 0, 字典默认的__missing__ 方法不实现会报错(KeyError)
def __missing__(self, key):
'The count of elements not in the Counter is zero.'
# Needed so that self[missing_item] does not raise KeyError
return 0
总结
总体来说,Counter通过对内置字典类型的继承重写来的实现,比较简洁,逻辑也很清楚,从源码中可以学到很多标准库里提供的很多的不常见的方法的使用,可以使代码更加简洁,思路更加流畅。
Python Counter() 的实现的更多相关文章
- Python Counter class
Counter class https://docs.python.org/2/library/collections.html#collections.Counter # class collect ...
- 利用Python Counter快速计算出现次数topN的元素
需要用Python写一段代码,给定一堆关键词,返回出现次数最多的n个关键字. 第一反应是采用一个dict,key存储关键词,value存储出现次数,如此一次遍历即可得出所有不同关键词的出现次数,而后排 ...
- python counter、闭包、generator、解数学方程、异常
1.counter 2.闭包 3.generator 4.解数学方程 5.异常 1.python库——counter from collections import Counter breakfast ...
- Python Counter()计数工具
Table of Contents 1. class collections.Counter([iterable-or-mapping]) 1.1. 例子 1.2. 使用实例 2. To Be Con ...
- Python Counter
from collections import Counter print(Counter("宝宝今年特别喜欢王宝强")) # 计数 lst = ["jay", ...
- 【转】python Counter模块
>>> c = Counter() # 创建一个新的空counter >>> c = Counter('abcasdf') # 一个迭代对象生成的counter & ...
- Python入门(二,基础)
一,基本语法 Python标识符 在python里,标识符有字母.数字.下划线组成. 在python中,所有标识符可以包括英文.数字以及下划线(_),但不能以数字开头. python中的标识符是区分大 ...
- python 基础知识点整理 和详细应用
Python教程 Python是一种简单易学,功能强大的编程语言.它包含了高效的高级数据结构和简单而有效的方法,面向对象编程.Python优雅的语法,动态类型,以及它天然的解释能力,使其成为理想的语言 ...
- python学习第一天内容整理
.cnblogs_code { width: 500px } 一.python 的历史 (摘自百度百科,了解就ok) Python[1] (英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn ...
随机推荐
- c# 运行时替换某文件源代码(将XML 转换成 某个枚举并写入源文件)
var sr = new StreamReader(Server.MapPath("~/WEB-INF/rule.config")); XmlDocument doc = new ...
- 轻松学习Linux之理解Shell的硬链接与软连接
大家在学习linux的过程中常常遇到一些模糊且容易混淆的概念比如什么是硬链接和软链接,他们有什么区别? 软连接有点象windows中的快捷方式,连接和目标文件具有相同的节点,而硬连接就好象重新复制 ...
- virtualenv 和 virtualenvwrapper 实践
virtualenv 首先来聊一下 virtualenv 是个什么鬼. 在使用 Python 开发的过程中,工程一多,难免会碰到不同的工程依赖不同版本的库的问题:亦或者是在开发过程中不想让物理环境里充 ...
- <转载>浅谈C/C++的浮点数在内存中的存储方式
C/C++浮点数在内存中的存储方式 任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100.则在Intel CPU架构的系统中 ...
- java getEnv不区分大小写 getProperty区分大小写
System.out.println(System.getenv("JAVA_HOME")); System.out.println(System.getenv("Pat ...
- Add mappings to an Elasticsearch index in realtime
Changing mapping on existing index is not an easy task. You may find the reason and possible solutio ...
- mysql中 group_concat长度限制
//这个函数有长度限制,上了多次当.默认长度1024长度. select group_concat(id) from table; 要彻底修改,在MySQL配置文件(my.ini)中加上 group_ ...
- SSH下载的方法2
------------------------------下作下载方法二---------------------------------------------------String msg=n ...
- libcurl API:CURLOPT_REFERER的用法
1.当你用libcurlAPI来请求某些数据时,发现返回的数据是0,这时候你就要去尝试用CURLOPT_REFERER来伪造一个来路页面:具体设置什么值可以通过浏览器来获取:按f12,在network ...
- VNC 抓取远程桌面
VNC (Virtual Network Computing)是虚拟网络计算机的缩写.VNC 是一款优秀的远程控制工具软件,由著名的 AT&T 的欧洲研究实验室开发的.VNC 是在基于 UNI ...