Python Cookbook --第一章:数据结构和算法(1)
问题11:序列中出现次数最多的元素?
- Collections.Counter类就是专门为这类问题而设计的,他甚至有一个有用的most_common()方法直接给出答案。
>>> c = Counter("asdwqdsadsdqwdsafqefqdsadqwdasdaww")
>>> c
Counter({'d': 9, 'a': 6, 's': 6, 'w': 5, 'q': 5, 'f': 2, 'e': 1})
>>> c.most_common(4)
[('d', 9), ('a', 6), ('s', 6), ('w', 5)]
- 关于Counter的其他应用
Counter({'d': 9, 'a': 6, 's': 6, 'w': 5, 'q': 5, 'f': 2, 'e': 1})
>>> c["z"]=1 //直接将c当成字典使用。
>>> c
Counter({'d': 9, 'a': 6, 's': 6, 'w': 5, 'q': 5, 'f': 2, 'e': 1, 'z': 1})
>>> new = "dasdxxzzsdasdwdawdwadadadsddwdwadw"
>>> c.update(new) //直接更新c
>>> c
Counter({'d': 22, 'a': 13, 'w': 11, 's': 10, 'q': 5, 'z': 3, 'f': 2, 'x': 2, 'e': 1})
- 关于Counter的数学运算
>>> a = Counter("adsdaasdsdsdwdadsd")
>>> b = Counter("fdfadsadadqwdsadsasadadw")
>>> a
Counter({'d': 8, 's': 5, 'a': 4, 'w': 1})
>>> b
Counter({'d': 8, 'a': 7, 's': 4, 'f': 2, 'w': 2, 'q': 1})
>>> c = a+b
>>> c
Counter({'d': 16, 'a': 11, 's': 9, 'w': 3, 'f': 2, 'q': 1})
>>> d = a-b
>>> d //若数值为负数,直接丢弃。
Counter({'s': 1})
问题12:通过某个(某几个)关键字排序一个字典列表。
- 通过使用operator模块的itemgetter函数,可以非常容易地排序这样地数据结构。
rows = [
... {"fname":"Brian","lname":"Jones","uid":1003},
... {"fname":"David","lname":"Beazlie","uid":1002},
... {"fname":"Jods","lname":"Cleese","uid":1001},
... {"fname":"Big","lname":"Jones","uid":1004},
... ]
>>> from operator import itemgetter
>>> rows_by_fname = sorted(rows,key=itemgetter("fname"))
>>> rows_by_fname
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazlie', 'uid': 1002},
{'fname': 'Jods', 'lname': 'Cleese', 'uid': 1001}]
- Itemgetter()函数也支持多个keys,代码如下:
>>> rows_by_flname = sorted(rows,key=itemgetter("fname","lname"))
>>> rows_by_flname
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazlie', 'uid': 1002},
{'fname': 'Jods', 'lname': 'Cleese', 'uid': 1001}]
问题13:排序不支持原生比较的对象。
- 解释一下原生比较(个人理解):就是直接扔到sorted()里没有办法排序的,只能通过指定其对象中的一个属性来排序。如下面例子,不使用key = lambda u: u.user_id,则会运行报错。错误如下:
File "14.1.py", line 12, in sort_notcompare
print(sorted(users))
TypeError: unorderable types: User() < User()
- 像数字一类的可以直接扔进sorted()函数中,直接排序。
- 内置的sorted()函数有一个关键字参数key,可以传入一个可迭代对象,这个对象对每个传入的对象返回一个值,这个值会被sorted用来排序这些对象。例如:
from operator import attrgetter
class User:
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return "User({})".format(self.user_id)
def sort_notcompare():
users = [User(23), User(4), User(43)]
print(users)
# print(sorted(users, key = lambda u: u.user_id))
print(sorted(users, key = attrgetter("user_id"))) #这句可以代替上面语句
sort_notcompare()
运行结果:
[User(23), User(4), User(43)]
[User(4), User(23), User(43)]
问题14:通过么个字段将记录分组
有一个字典或者实例的序列,然后根据某个特定的字段比如data来分组迭代访问。
- itertools.groupby()函数对于这样的数据分组操作非常实用。为了演示,假设有如下字典列表:
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'},
]
- 假设现在想按data分组后的数据块上进行迭代。为了这样做,首先需要按照指定的字段排序,然后调用itertools.groupby()函数:
>>> rows.sort(key = itemgetter("date")) #这里若不排序,会得不到预期的结果。
>>> rows
[{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/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'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
]
>>> 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() 函数扫描整个序列并且查找连续相同值 (或者根据指定 key 函数返回值相同) 的元素序列。
- 如果没有提前排序,可能会得到和预期不一样的结果:就比如上面的例子,当没有排序直接使用groupby()时,会出现下面的结果:
07/01/2012
{'address': '5412 N CLARK', 'date': '07/01/2012'}
07/04/2012
{'address': '5148 N CLARK', 'date': '07/04/2012'}
07/02/2012
{'address': '5800 E 58TH', 'date': '07/02/2012'}
07/03/2012
{'address': '2122 N CLARK', 'date': '07/03/2012'}
07/02/2012
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/01/2012
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/04/2012
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
问题15:过滤序列元素
有一个数据序列,利用一些规则从中提取需要的值或者是缩短序列。
- 最简单的过滤序列元素的方法就是使用列表推导式。
>>> mylist = [1,4,-5,20,-7,2,3,-1]
>>>
>>> [n for n in mylist if n>0]
[1, 4, 20, 2, 3]
- 使用列表推导的一个潜在缺陷就是:如果输入很大数据,那么就会产生一个非常大的结果集,占用大量内存。故采用生成器表达式迭代产生过滤的元素。
>>> gen = (n for n in mylist if n>0) #使用生成器
>>> for i in gen:
... print(i)
...
1
4
20
2
3
- 如果过滤规则比较复杂,不能使用列表推导式和生成器表达式,则需要使用内建的filter()函数。
- Filter()函数创建的是一个迭代器对象,因此想要得到一个列表的话,就需要用list()去转换。
>>> values = [1,2,3,4,5,6,7,8,9]
>>> def is_even(num):
... if num%2==0:
... return True
... else:
... return False
...
>>> res = filter(is_even,values)
>>> res
<filter object at 0x000001B3A0819C88>
>>> list(res)
[2, 4, 6, 8]
- 另外一个值得关注的过滤工具就是itertools.compress(),它以一个iterable对象和一个相对应的Boolean选择器序列作为输入参数。然后输出iterable对象中对应选择器为True的元素。
>>> counts = [0,3,10,4,1,7,6,1]
>>> mores = [n>5 for n in counts]
>>> mores
[False, False, True, False, False, True, True, False]
>>> Data = [1,2,3,4,5,6,7,8]
>>> from itertools import compress
>>> compress(Data,mores) # 产生的是一个迭代器对象
<itertools.compress object at 0x000001B3A0819C18>
>>> list(compress(Data,mores))
[3, 6, 7]
问题16:从字典中提取子集
构造一个字典,它是另一个字典的子集。
- 最简单的方式是使用字典推导。
>>> prices = {
... 'ACME': 45.23,
... 'AAPL': 612.78,
... 'IBM': 205.55,
... 'HPQ': 37.20,
... 'FB': 10.75
... }
>>> p1 = {key:value for key,value in prices.items() if value>200}
>>> p1
{'AAPL': 612.78, 'IBM': 205.55}
>>> tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
>>> p2 = {key:value for key,value in prices.items() if key in tech_names}
>>> p2
{'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
- 通过创建一个元组序列然后把它传给dict()函数也可以实现。(速度比上面慢)
>>> p1 = dict((key,value) for key,value in prices.items() if value>200)
>>> p1
{'AAPL': 612.78, 'IBM': 205.55}
问题17:映射名称到序列元素
有一段通过下标访问列表或者元组元素的代码,但是这样会使得代码难以阅读,但是通过名称来访问元素会较好。
- Collections.nametuple()函数 (命名元组)通过使用一个普通的元组对象可以解决这个问题。这个函数实际上是一个返回Python中标准元组类型子类的一个工厂方法。需要传递一个类型名和所定制的字段给它,然后他就会返回一个类。示例如下:
>>> from collections import namedtuple
>>> s = namedtuple("ss",["name","age"]) #返回一个方法
>>> data = s("zhoulala","18") #让字段和数据绑定
>>> data
ss(name='zhoulala', age='18')
>>> data.name
'zhoulala'
>>> data.age
'18'
问题18:转换并同时计算数据
若需要在数据序列上执行聚集函数(比如sum(),min(),max()),但是首先需要转换或者过滤数据。
- 一个优雅的方式去结合计算与转换就是使用一个生成器表达式参数,示例如下:
>>> s = sum((x*x for x in nums)) # 使用生成器表达式,其()可以省略
>>> s
55
>>> s = sum([x*x for x in nums]) # 使用列表推导式,但是缺陷在于他首先会创建一个列表,当数据量较大时,这种仅仅使用一次的列表会非常浪费内存。
>>> s
55
问题19:合并多个字典或者映射
有多个字典或者映射,若想将它们从逻辑上合并为一个单一的映射后执行某些操作,比如查找值或者检查某些键是否存在。
- 假设必须在两个字典中执行查找操作 (比如先从 a 中找,如果找不到再在 b中找)。一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。比如:
>>> from collections import ChainMap
>>> a = {"x":1,"z":3}
>>> b = {"y":2,"z":4}
>>> c["x"]
1
>>> c["z"] # 返回的是第一个字典a中所对应的值
3
- ChainMap 接受多个字典并将它们在逻辑上变为一个字典。然后,这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表。对于字典的更新或者删除操作总是影响的是第一个字典。
>>> c
ChainMap({'x': 1, 'z': 3}, {'y': 2, 'z': 4})
>>> del c["y"] # 想删除第二个字典中的“y”,则会报错。
KeyError: 'y'
>>> del c["z"] # 删除“z”,’则第一个字典中的z被删除。
>>> c
ChainMap({'x': 1}, {'y': 2, 'z': 4})