文章目录
1. 将序列分解为单独的变量
问题
现在有一个包含N个元素的可迭代序列,怎么样将它里面的值解压后同时赋值给N个变量?
解决方案
- 任何的序列(或者是可迭代对象)可以通过一个简单的赋值操作来分解为单独的变量.
- 要求: 就是变量的总数和结构必须与序列相吻合.
解析
-
简单的序列赋值,可迭代序列的结构是单一的,变量也是单一的,然后一 一对应得上
-
序列赋值的时候,可迭代序 列的结构是嵌套结构,左边的变量可以是嵌套结构,也可以是单独的一个变量
-
如何只想要解压部分的变量,可以使用占位符_来占位其他的位置
2. 保留最后的N个元素以及左插入和右插入元素的快速操作
问题
在迭代操作或者是其他的操作的时候,如何只保留最后有限的几个元素的历史记录
解决方案
使用
collections.deque
解析
-
deque(iterable,[,maxlen])
maxlen
不指定或者指定为None
值的时候,表示deque
是任意大小的双端队列maxlen
如果为正数,当deque
的元素的个数已经满并且有元素从一端入队的时候,数量相同的旧元素将从另外一端出队(移除)- 双端队列可以实现左插入(
appendleft
)和右插入(append
),左弹出(popleft
)和右弹出(pop
),
并且时间复杂度是O(1)
,而list
的insert(0,item)
以及pop(0)
的时间复杂度是O(n)
-
方法速查表
append(x) appendleft(x) extend(iterable) extendleft(iterable) insert(i,x) pop() popleft() remove(x) clear() copy() count(x) index(x[,start[,stop]]) reverse() rotate(n=1)
3. 查找最大和最小的N个元素
问题
怎么样从一个数据集合中获得最大或者最小的N个元素列表
解决方案
heapq
模块有两个函数:nlargest()
和nsmallest()
解析
-
heapq- 堆排序算法
heapq
实现了一个最小堆排序算法 -
二叉树
树中的每个节点至多有两个子节点
-
满二叉树
树中除了叶子节点(最下面的节点),每个节点都有两个子节点
-
完全二叉树
完全二叉树是由满二叉树印出来的.一个满二叉树一定是一个完全二叉树,但是一个完全二叉树不一定是一个满二叉树.完全二叉树是指如果二叉树的深度为h,除却h层外,其它各层(1~h-1)层的节点数都达到最大个数,第h层所有的节点都连续集中在最左边,这就是完全二叉树
-
堆介绍
- 堆是一种数据结构,它是一颗完全二叉树.最小堆则是在堆的基础上增加了新的规则,它的根节点的值最小,而且它的任意节点的父节点的值都小于或者等于其左右节点的值.
- 最大堆: 最大堆确保父堆大于或者等于它的两个子堆
- 最小堆: 最小堆确保父堆小于或者等于它的两个子堆
heapq
模块实现的就是一个最小堆
-
heapq堆常用方法介绍
常用方法 说明 heappush(heap,item) 将item的值加入到heap当中,保持堆的不变性,heap必须是一个列表 heappop(heap) 弹出并返回heap的最小元素,保持堆的不变性.如果堆为空,抛出IndexError heappushpop(heap,item) 将item放入到堆中,然后弹出最小的元素.比先调用heappush()再调用heappop()效率高 heapify(x) 将list x 转换成堆,原地,线性时间内 heapreplace(heap,item) 弹出并返回heap中最小的一项,同时推入新的item.堆的大小不变.如果堆为空则引发IndexError异常 merge(*iterables,key=None,reverse=False) 将多个已排序的输入合并为一个已经排序的输出,返回已经排序的Iterator,按照key指定的Function规则进行排序 nlargest(n,iteralbe,key=None) 从iterable所定义的数据集中返回前n个最大元素组成的列表.如果提供了key则其应该指定一个单参数的函数,用于从iterable的每个元素中提取比较键(例如 key=str.lower).等价于:sorted(iteralbe,key=key,reverse=True)[:n] nsmallest(n,iterable,key=None) 从iterable所定义的数据集中返回前n个最小元素组成的列表.如果提供了key则应该指定一个单参数的函数,用于从iterable的每个元素中提取比较键(例如key=str.lower)等价于:sorted(iterable,key=key)[:n]
-
注释
- 当要查找的元素的个数相对较小的时候,函数
nlargest()
和nsmallest
比较适合 - 如果只是要查找唯一的最大和最小元素,则
max()
和min()
比较合适 - 如果要查找的元素的个数和元素的总个数差不多的时候,使用
sorted(items,key=None,reverse=False)[:N]
比较合适
- 当要查找的元素的个数相对较小的时候,函数
4. 实现一个优先级队列
问题
实现一个按照优先级排序的队列?并且每次这个队列上的
pop
操作总是返回优先级最高的那个元素
解决方案
- 使用
heapq
模块实现一个堆- 当堆里面插入的都是元组的时候,默认会按照元组的第一个元素进行排序,如果第一个元素相等,会按照第二个元素进行排序
- 将优先级和插入的时候的顺序记录成一个索引以及元素的值封装成一个三元的元组放入堆中
- 将优先级取负数,然后再取最小值,得到的就是最大的优先级.(因为heapq是最小堆排序结构),
所以这里的传入的优先级是正,是负,取决于你优先级数值越大是越优先,还是数值越小越优先
解析:
-
堆排序(当堆的元素是元组的时候)
-
优先级队列
构造一个元组
tup = (-priority,index,item)
作为堆的元素,放入到堆中.会按照-priority
进行排序
如果-priority
相同,则会按照插入的顺序进行排序,index记录的就是插入的时候的索引,递增的
返回的时候返回元组的最后一个元素即可
5. 字典中的键映射多个值
问题
怎么样实现一个键对应多个值的字典(也叫
multidict
)
解决方案
- 第一反应就是创建一个字典,然后字典的值采用容器类型的数据结构,多个值放到容器当中
- 至于是选择
list
,set
,tuple
,要看你的需求,如果需要知道位置并且是可改变的,就用list
,
如果是不需要索引并且要求无重复,就使用set
,如果需要知道位置,并且值是不可以改变的,你就用tuple
解析
collections.defaultdict详解
default_dict
实现的默认值的功能是通过什么实现的呢?通过__missing__()
这个方法- 也就是说,在访问
defaultdict
的时候,实际上是调用的__getitem__()
方法,当这个键不存在的时候,
__getitem__()
会去调用__missing__()
方法__missing__()
方法做的事情是这样的,如果提供了工厂方法,则会调用下面的代码self[key] = value = self.default_factory()
如果没有提供工厂方法,默认工厂方法是None,则会raise KeyError
异常.
4>defaultdict
初始化的时候要使用一个工厂方法去初始化,如果么有提供,工厂方法就变成了None
自定义一个defaultdict类
-
defaultdict 一个键映射多个值举例
6. 字典排序
问题
你想创建一个字典,并且在迭代或者序列化这个字典的时候能够控制元素的顺序
解决方案
- 通常我们将字典是无序的,因为它是根据hash表进行访问的,但是如果你想要控制一个字典的顺序也是可以的,使用
collections
模块的OrderedDict
类.在迭代操作的时候,它会保持元素被插入的时候的顺序- 当你想构建一个将来需要序列化或编码成其他格式的映射的时候,也就是说你构建的数据有顺序要求,
OrderedDict
是非常有用的.比如精确的控制JSON
编码后字段顺序OrderedDict
内部维护着一个根据键插入顺序排序的双向链表.每次插入一个新元素的时候,它会被放到链表的尾部.对于一个已经存在的键的重复赋值不会改变键的顺序.- 需要注意的是,一个
OrderedDict
的大小是普通字典的两倍,因为它内部维护着另外一个链表.所以如果数据量大,你要考虑内存消耗问题的影响值不值得
解析
OrderedDict 会记录住键值对插入时候的顺序
7 . 字典的运算
问题
怎么样在数据字典中执行一些计算操作(针对值,比如求最小值,最大值,排序等等)
解决方案
- 使用
zip(dict.vlaues(),dict.keys())
这种方式最简单,并且返回的也是一对数据- 还可以通过
min 或者 max 或者 sorted 的方式,通过key=值的方式只排序值
解析
zip(dict.values(),dict.keys())的妙用
-
字典计算按照字典的值去比较和计算去计算
妙用dict.items()
8. 查找两个字典的相同点
问题
怎么样在两个字典中寻找相同点(比如相同的键,相同的值等等)
解决方案
- 为了寻找两个字典的相同点,可以简单的在两个字典的
keys()
或者items()
方法返回结果上执行集合操作,它们可以直接进行集合操作,不需要转换为集合- 但是字典的
values()
返回的数据映射,就不支持集合操作,因为值允许有重复的
解析
-
通过集合操作键
-
寻找值相同的字典
9. 删除序列相同元素并保持顺序
问题
怎么样在一个序列上面保持元素顺序的同时消除重复的值
解决方案
- 序列上的值都是
hashable
通过集合好生成器就可以实现- 序列上的值如果有结构形数据,不是
hashable
的,那就要指定一个key
制定一个规则- 如果不需要顺序,则直接使用
set()
即可
解析
使用生成器和集合
使用列表推导式和切片成员判断的方式
10. 命名切片
问题
代码中包含大量的切片,如果想增加代码的易读性,你可以使用命名切片来实现
解决方案
- 内置的
slice()
函数创建一个切片对象,所有使用切片的地方都可以使用切片对象- 可以通过调用
items[slice]
的方式访问它- 并且切片对象你还可以查看它的一些属性
a.start(切片的起点)
,a.stop切片的终点
,a.step(切片的步长)
a.indices(size)
调用切片的方法将它映射到一个已知大小的序列上.这个方法返回一个三元组
(start,stop,step)
,所有的值都会被缩小,直到适合这个已知序列的边界为止,这样就可以避免IndexKeyError
异常
解析
11. 序列中出现次数最多的元素
问题
怎么样找出一个序列中出现最多的元素呢?
解决方案
collections.Counter
类是专门为这类问题而设计的,它有一个有用的most_common()
方法直接给出了答案
解析
Counter
对象可以接收任意的由可哈希(hashable
)元素构成的序列对象.在底层实现上,一个Counter
就是一个字典,将的key
是元素,value
是元素出现的次数.counter.most_common(n=None)
返回出现次数最多的前N个元素和次数的列表,如果n=None,则返回所有的(元素,次数)列表
Counter
实例可以跟数学运算符操作相结合
12. 通过某个关键字排序一个字典列表
问题
你有一个字典列表,你想根据某个或者某几个字典字段来排序这个列表
解决方案
- 通过使用
operator
模块的itemgetter
函数,可以非常容易的排序这样的数据结果.itemgetter()
函数就是负责创建一个callable
对象的,operator.itemgetter()
函数有一个被rows
中的记录用来查找值的索引参数.可以是一个字典的键名称,一个整型值或者任何能够传入一个对象的__getitem__
方法的值.如果你传入多个索引参数给itemgetter()
,它生成的callable
对象会返回一个包含所有元素的元组,并且sorted()
函数会根据这个元组中元素顺序去排序.- 当然你也可以通过匿名函数来代替
itemgetter()
方法,但是itemgetter()
会高效一点itemgetter()
同时适用于min()
和max()
函数
解析
13. 排序不支持原生比较的对象
问题
你想排序类型相同的对象,但是他们不支持原生的比较操作
解决方案
- 内置的
sorted()
函数有一个关键字参数key
,可以传入一个callable
对象给它,这个callable
对象对每个传入的对象返回一个值,这个值被sorted
用来排序这些对象.比如,如果你再应用程序里面有一个User
实例序列,并且你希望通过它们的userId
属性进行排序,你可以提供一个以User
实例作为输入并输出对应user_id
值的callable
对象.- 当然这种获取对象属性的方式在
operator
里面也有对应的方法attrgetter()
,也可以使用labmda函数代替
attrgetter()
通常会运行的快点,并且还能同时允许多个字段进行比较.- 同时支持
min()
和max()
操作
14. 通过某个字段将记录分组
问题
你有一个字典或者实例的序列,然后你想根据某个特定的字段比如
date
来分组迭代访问
解决方案
itertools.groupby()
函数对于这样的数据分组操作非常的实用.groupby()
函数扫描整个序列并且查找连续相同值(或者根据指定key函数返回值相同)的元素序列.在每次迭代的时候,它会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素之全部等于上面那个值得组中所有对象- 一个非常重要的步骤就是要根据指定的字段将数据排序.因为
groupby()
仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果
15. 过滤序列元素
问题
你有一个数据序列,想利用一些规则从中提取出来需要的值或者是缩短序列
解决方案
最简单的过滤序列元素的方法就是使用列表推导式
使用列表推导式的一个潜在的缺陷就是如果输入非常大的时候会产生一个非常大的结果集,占用大量的内存.如果你对内存比较敏感,那么你可以使用生成器表达式迭代产生过滤掉的元素.比如
有时候过滤的规则比较复杂,不能简单的在列表推导或者生成器表达式中表达出来.比如,假设过滤的时候需要处理一些异常或者其他复杂的情况这个时候你可以将过滤代码放到一个函数中,然后使用内建的filter()
函数,例如:
filter()
函数创建了一个迭代器,因此如果你想要得到一个列表的话,就得像示例那样使用list()
去转换
过滤操作的一个变种就是将不符合条件的值用心的值代替,而不是丢弃它们.比如,在一列数据中你可能不仅仅是想要找到正数,而且还想将不是正数的数替换成指定的数.通过将过滤条件放到表达式中去,可以容易解决这问题
另外一个过滤的函数叫itertools.compress()
函数,它以一个iterable
对象和一个相应的Boolean
选择器序列作为输入参数.然后输出iterable
对象中对应选择器为True
的元素.当你需要用另外一个相关联的序列来过滤掉某个序列的时候,这个函数是非常有用的.比如,假如你有下面两组数据
当然你可以可以通过列表推导式来实现
16. 从字典中提取子集
问题
你想构造一个字典,它是另外一个字典的子集
解决方案
最简单的方式就是使用字典推导式.比如:
大多数的情况下,字典推导式是可以做到的,通过创建一个元祖序列然后把它传给dict()
函数也能实现.比如:
但是,字典推导式的表意更加的清晰,并且实际上也会运行的更快一些.
17. 映射名称到序列元素
问题
你有一段通过下标访问列表或者元祖中元素的代码,但是这样有时候会使得你的代码难以阅读,于是你想通过名称来访问元素
解决方案
collections.nametuple()
函数通过使用一个普通的元祖对象来帮助你解决这个问题.这个函数实际上是一个返回Python
中标准元祖类型子类的一个工厂方法.你需要传递一个类型名和你需要的字段给它,然后它就会返回一个类,你可以初始化这个类,为你定义的字段传递值等.
尽管namedtuple
的实例看起来像一个普通的类实例,但是它跟元祖类型是可以交换的,支持所有的普通的元祖的操作,比如索引和解压.比如:
命名元祖的一个主要的用途是将你的代码从下标操作中解脱出来.因此,如果你从数据库中调用中返回一个很大的元祖列表,通过下标去操作其中的元素,当你在表中添加了新的列的时候你的代码可能就出错了.但是如果你使用了命名元祖,那么久不会有这样的顾虑了.
下面使用普通的元祖代码:
命名元祖另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间.如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元祖会更加的高效.但是需要注意的是,不像字典那样,一个命名元祖是不可更改的,比如:
如果你真的需要改变属性的值,那么可以使用命名元祖实例的_replace()
方法,它会创建一个全新的命名元祖并将对应的字段用心的值取代.比如:
_replace()
方法还有一个很有用的特性就是当你的命名元祖拥有可选或者缺失字段的时候,它是一个非常方便的填充数据的方法.你可以先创建一个包含缺省值的原型元祖,然后使用_replace()
方法创建新的值被更新过的实例.比如:
18. 转换并同时计算数据
问题
你需要再数据序列上执行聚集函数(比如
sum()
,min()
,max
),但是首先你需要先转换或者过滤数据
解决方案
一个非常优雅的方式去结合数据计算与转换就是使用一个生成器表达式参数.比如,如果你想要计算平方和,可以像下面这样做:
下面是更多的例子
上面的示例向你演示了当生成器表达式作为一个单独参数传递给函数时候的巧妙语法(你并不需要多加一个括号).比如,下面这些语句是等效的
使用一个生成器表达式作为参数会比先创建一个临时列表更加高效和优雅.比如,如果你不使用生成器表达式的话,你可能会考虑使用下面的实现方式:
19. 合并多个字典或者映射
问题
现在有多个字典或者映射的时候,你想将它们从逻辑上合并为一个单一的映射后执行某些操作,比如查找值或者检查某些键是否存在.
解决方案
假设你有如下两个字典:
a = {"x":1,"z":3} b = {"y":2,"z":4}
现在假设你必须在这两个字典中执行查找操作(比如先从a
中找,如果找不到再在b
中找).一个非常简单的解决方案就是使用collections
模块中的ChainMap
类.比如:
一个ChainMap
接受多个字典并将它们在逻辑上变成一个字典.然后,这些字典并不是真的合并在一起了,
chainMap
类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表.大部分字典操作都是可以正常使用的,比如:
如果出现重复键,那么第一次出现的映射值将会被返回.因此,例子程序中的c["z"]
总是会返回字典a
中对应的值,而不是b
中对应的值
对于字典的更新或者删除操作总是会影响列表中的第一个字典.比如: