Python-标准库collections的使用

保留最后N个元素

在迭代操作或者其他操作的时候,怎么样只保留最后有限几个元素的历史记录?保留有限历史记录正式collections.deque大显身手的时候了,下面简单介绍一下collections.deque的使用:

from collections import deque

d = deque([1,2,3], maxlen=3)
d.append(4)
print d

#=======打印结果如下=======
deque([2, 3, 4], maxlen=3)

deque设置队列的长度时,当队列里的元素逐渐递增,超过队列所限制的长度时,deque会将第一个元素从队列中剔除,以保持队列长度设定的有效性。我们还可以定义一个不限长度的队列:

from collections import deque

d = deque()
d.append(1)
d.append(2)
d.append(3)

print d     # deque([1, 2, 3])

# 在最左边添加
d.appendleft(0)
print d     # deque([0, 1, 2, 3])

# 删除最后一个元素,并返回被删除的值
print d.pop()   # 3 
print d         # deque([0, 1, 2])

# 删除第一个元素,并返回被删除的值
print d.popleft()   # 0
print d             # deque([1, 2])

字典中的键映射多个值

怎么样实现一个键映射多个值的字典(也叫multidict)?一个字典就是一个键对应一个单值的映射,如果你想一个键映射多个值,那么你需要将这多个值放到另外的容器当中,比如列表或者集合中。比如,你可以向下面那样定义字典:

info = {
    "a": [1, 2, 3]
    "b": {1, 2, 3}
}

这样,键a与键b都实现了一个键映射多个值的需求。如果你想保证元素的插入顺序,那么就应该使用列表,如果你想去掉重复元素,那么就应该使用集合。你可以使用collections模块的defaultdict来构造这样的字典。defaultdict的一个特征就是会初始化每个key刚开始对应的值,所以你只需要关注添加操作即可,如:

from collections import defaultdict

d = defaultdict(list)
d["a"].append(1)
d["a"].append(2)
d["b"].append(3)

print d

# ===========打印结果如下===========
defaultdict(<class 'list'>, {'a': [1, 2], 'b': [3]})

defaultdict会自动为我们将要访问的键(就算目前字典中并不存在这样的键)创建映射实体,如果你并不需要这样的特性,只需要在一个普通字典上使用setdefault()方法来代替,比如:

info = dict()
info.setdefault("a", []).append(1)

print info
# ===========打印结果如下===========
{"a": [1]}

一般来说,创建一个多值映射字典是很简单的,但是,如果你选择自己实现的话,那么可能对值的初始化会有点麻烦。你可能会像下面这样来实现:

info = {}
for key, value in pairs:
    if key not in info:
        info[key] = []
    info[key].append(value)

但是,你如果使用defaultdict就更加简单了:

from collections import defaultdict

info = defaultdict(list)
for key, value in pairs:
    info[key].append(value)

字典的排序

我们都知道字典是无序的,如果你想创建一个字典,在迭代或者序列化这个字典的时候想控制元素的顺序,那么该如何做?为了能控制一个字典中元素的顺序,你可以使用collections模块中的OrderedDict类,它会保持元素被插入时的顺序,示例如下:

from collections import OrderedDict
d = OrderedDict()
d["a"] = 1
d["b"] = 3
d["c"] = 2
d["d"] = 4
for key in d:
    print(key, d[key])

#==============打印结果如下==============
a 1
b 3
c 2
d 4

下面我们将上面的OrderedDict进行序列化:

import json
info = json.dumps(d)
print info

#==============打印结果如下==============
{"a": 1, "b": 3, "c": 2, "d": 4}

OrderedDict内部维护着一个根据键的插入顺序排序的双向链表,每次当一个新的元素插入进来,它会被放到链表的尾部,对于一个已存在的键重复复制不会改变它的顺序。值得注意的是,一个OrderedDict的大小是一个普通dict的两倍,因为它内部维护着另一个链表。如果你要构建一个大量OrderedDict实例的数据结构的时候,那么你需要权衡一下OrderedDict所带来的好处以及内存消耗的影响。

获取列表出现次数最多的元素

怎么样找出一个列表中出现次数最多的元素呢?collections.Counter类就是专门为这类问题产生的,具体用法如下:

from collections import Counter

info = ["a", "b", "c", "d", "e", "f", "d", "e", "f", "g", "i", "e", "g", "a", "j", "w", "g", "f", "i"]
data = Counter(info)
# 获取出现次数多的3个元素
info = data.most_common(3)
print info

# ==============打印结果如下==============
[('e', 3), ('f', 3), ('g', 3)]

Counter接收任意的hashable序列对象,它的底层实现就是一个dict,将元素映射到它出现的次数上。

Counter还可以跟一些数学运算相结合,如下:

from collections import Counter
a = Counter(["a", "b", "c"])
print a                         # Counter({'a': 1, 'b': 1, 'c': 1})

b = Counter(["b", "c", "d"])
print b                         # Counter({'b': 1, 'c': 1, 'd': 1})

print a + b                     # Counter({'b': 2, 'c': 2, 'a': 1, 'd': 1})
print a - b                     # Counter({'a': 1}) 

命名元组

命名元组为collections下的一个函数namedtuple(),这个函数其实是Python标砖类型元组子类的一个工厂方法。你需要传递一个类名和你需要的字段给它,然后就会返回一个类,你可以初始化这个类,为你定义的字段传值:

from collections import namedtuple
Student = namedtuple("Student", ["name", "age"])
s = Student("laozhang", 20)
print s.name, s.age

# ==============打印结果如下==============
laozhang 20

namedtuple()看起来是返回了一个类,并且我们将它进行了实例化,得到变量s。咋看一下,s似乎是一个类的实例化对象,但是它跟元组类型是可交换的,并且支持所有普通元组操作,比如:

print len(s)
name, age = s
print name, age

# ==============打印结果如下==============
2
laozhang 20

命名元组另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间。如果你需要构建一个非常大的包含字典的数据结构,那么命名元组会更加高效。值得注意的是,命名元组跟字典不同的是,命名元组不支持修改,强行修改将会报错。

命名元组不支持修改,强行修改将会报错,如果你对命名元组的属性的值进行修改,那么你可以使用命名元组的_replace()方法,它会创建一个新的命名元组,并且用新的值进行取代:

s = s._replace(name="laoli")
print s.name, s.age

# ==============打印结果如下==============
laoli 22

从结果可以看出,属性的值真的被“替换”了。

合并多个字典或映射

现在有多个字典或映射,你想将它们从逻辑上合并成一个单一的映射后执行某些操作,比如查找值或检查某些键是否存在,假设有这样两个字典:

a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}

如果你想进行查找的操作,那么你就必须在ab两个字典中进行查找,那么未免过于繁琐。collections模块的ChainMap类很好的解决了这一难题:

from collections import ChainMap
c = ChainMap(a, b)
print list(c.keys())
print list(c.values())
print c["x"], c["y"], c["z"]

# ==============打印结果如下==============
['x', 'z', 'y']
[1, 4, 2]
1 2 4

一个ChainMap类接受多个字典并在逻辑上将他们变成了一个字典,然而,这些字典并不是真的合并在了一起,而是ChainMap类内部创建了一个可以容纳这些字典的列表,并定义了一些字典的常用操作来遍历这个列表,大部分字典操作都是可以用的。

ChainMap在将这些字典进行合并“操作”时,遇到相同的键,那么第一次映射的值将会被返回。因此例子中,c["y"]的值为2,而不是3。对于字典的更新和删除也总是只会影响第一个字典,如下:

c["x"] = 5
c["w"] = 0
del c["y"]
print a

# ==============打印结果如下==============
{'x': 5, 'w': 0}

ChainMap对于编程语言中变量的作用范围变量是很有用的:

from collections import ChainMap

c = ChainMap()
c["x"] = 1

c = c.new_child()
c["x"] = 2

c = c.new_child()
c["x"] = 3

print c["x"]
c = c.parents
print c["x"]
c = c.parents
print c["x"]

# ==============打印结果如下==============
3
2
1

作为ChainMap的替代,你可能会想到使用字典的update将两个字典进行合并。但是,原字典的数据发生了改变,并不会映射到新字典,而ChainMap可以。情况如下代码:

a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}
b.update(a)
a["x"] = 5
print a
print b
# ==============打印结果如下==============
{'x': 5, 'y': 2}
{'y': 2, 'z': 4, 'x': 1}


from collections import ChainMap
c = ChainMap(a ,b)
a["x"] = 5
print a
print c["x"], c["y"], c["x"]
# ==============打印结果如下==============
{'x': 5, 'y': 2}
5 2 4
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值