写在前面


  • 博文为《Python Cookbook》读书笔记整理
  • 读了这本书,才发现Python数据处理远比我想的要强大
  • 理解不足小伙伴帮忙指正

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波


数据结构和算法

Python内置了许多非常有用的数据结构,比如列表(list)、集合(set)以及字典(dictionary)、元组(tuple)。在collections模块中也包含了针对各种数据结构的解决方案。

将序列分解为单独的变量

我们有一个包含N个元素的元组或序列,现在想将它分解为N个单独的变量。

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ python3
Python 3.9.10 (main, Jan 16 2022, 17:12:18)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> p = (4,5)
>>> x,y = p
>>> x
4
>>> y
5
>>>

居然可以这样,长见识了,类似于JavaScript ES6中的解构赋值,如果当成函数对象来看,可以看做是拆包

>>> data = [ 'ACME',50,91.1,(2012,12,21)]
>>> n,s,p,d = data
>>> n,s
('ACME', 50)
>>> d
(2012, 12, 21)
>>>

当然,也支持深层次解构

>>> data = [ 'ACME',50,91.1,(2012,12,21)]
>>> name,shares,price,(year,mon,day)=data
>>> year
2012
>>> mon,day
(12, 21)
>>>

如果元素的数量不匹配,将得到一个错误提示。

>>> p = (4,5)
>>> x,y,z = p
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)
>>>

实际上不仅仅只是元组或列表,只要对象恰好是可迭代的,那么就可以执行分解操作。这包括字符串、文件、迭代器以及生成器

>>> s = 'love'
>>> a,b,c,d = s
>>> a,b
('l', 'o')
>>> d
'e'
>>>

当做分解操作时,想丢弃某些特定的值。可以用 '_'充当占位符,,这个在JavaScript ES6Golang也都支持。

>>> data = [ 'ACME',50,91.1,(2012,12,21)]
>>> _,_,a,_=data
>>> a
91.1
>>>

从任意长度的可迭代对象中分解元素

需要从某个可选代对象分解出N个元素,但是这个可迭代对象的长度可能超过N,这会导致出现分解的值过多(too many values to unpack)的异常。

>>> record=('Dave','davedexample.com','773-555-1212','1847-555-1212')
>>> name,email,*phone_numbers = record
>>> name
'Dave'
>>> phone_numbers
['773-555-1212', '1847-555-1212']
>>>

类似于Java方法传值的可变参数一样,但是要比java高级的多,java的可变参数只能最后一个,python 则可以在任意位置

>>> record=('Dave','davedexample.com','1224965096@qq.com','773-555-1212')
>>> name,*email,phone = record
>>> name
'Dave'
>>> email
['davedexample.com', '1224965096@qq.com']
>>>

* 式的语法在选代一个变长的元组序列时尤其有用

records = [('foo', 1, 2),('bar', 'hello'),('foo', 3, 4),
]def do_foo(x, y):print('foo', x, y)def do_bar(s):print('bar', s)for tag, *args in records:if tag == 'foo':do_foo(*args)elif tag == 'bar':do_bar(*args)

字符串拆分是的使用 : ''.split('')

>>> line='nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
>>> uname,*fields,homedir,sh=line.split(':')
>>> uname
'nobody'
>>> sh
'/usr/bin/false'
>>> fields
['*', '-2', '-2', 'Unprivileged User']
>>>

也可 '*' 和 '_' 两个连用,丢弃多个变量

>>> uname,*_,homedir,sh=line.split(':')
>>> sh
'/usr/bin/false'
>>>

求和递归

items=[1,10,7,4,5,9]
def sum(items): head,*tail=items return head+sum(tail) if tail else head

保存最后N个元素(队列)

我们希望在迭代或是其他形式的处理过程中对最后几项记录做一个有限的历史记录统计。

保存有限的历史记录可算是collections.deque的完美应用场景了

打印满足条件的最后5条记录

#deque(maxlen=N)创建了一个固定长度的双端队列
from collections import dequedef search(lines, pattern, history=5):previous_lines=deque(maxlen=history)for line in lines:if pattern in line:# 生成器yield line, previous_linesprevious_lines.append(line)
# Example use on a file
if __name__ == '__main__':with open('somefile.txt') as f:for line, prevlines in search(f,'python',5):for pline in prevlines:print(pline, end='')print(line, end='')print('-1'*20)

deque(maxlen=N)创建了一个固定长度的双端队列

>>> from collections import deque
>>> q = deque()
>>> q.append(1)
>>> q.append(2)
>>> q.append(3)
>>> q
deque([1, 2, 3])
>>>

尽管可以在列表上手动完成这样的操作(append、del),但队列这种解决方案要优雅得多,运行速度也快得多。从队列两端添加或弹出元素的复杂度都是O(1)。这和列表不同,当从列表的头部插入或移除元素时,列表的复杂度为O(N)

找到最大或最小的N个元素

我们想在某个集合中找出最大或最小的N个元素。

heapq模块中有两个函数nlargest()和nsmallest(),当所要找的元素数量相对较小时,函数nlargest()和nsmallest()才是最适用的,nlargest()和nsmallest()的实际实现会根据使用它们的方式而有所不同,可能会相应作出一些优化措施(比如,当N的大小同输入大小很接近时,就会采用排序的方法)。

>>> import heapq
>>> nums=[1,8,2,23,7,-4,18,23,42,37,2]
>>> heapq.nlargest(3,nums)
[42, 37, 23]
>>> heapq.nsmallest(3,nums)
[-4, 1, 2]
>>>

这两个函数都可以接受一个参数key,从而允许它们工作在更加复杂的数据结构之上

import  heapqportfoli = [{'name': 'AAPL', 'shares': 50, 'price': 543.22},{'name': 'FB', 'shares': 200, 'price': 21.09},{'name': 'HPQ', 'shares': 35, 'price': 31.75},{'name': 'YHOO', 'shares': 45, 'price': 16.35},{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
print(heapq.nsmallest(2,portfoli,key=lambda s: s['price']))
print(heapq.nlargest(2,portfoli,key=lambda s: s['shares']))
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.09}]
[{'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]

如果正在寻找最大或最小的N个元素,且同集合中元素的总数目相比,N很小,那么heapq.heapify(heap)函数可以提供更好的性能。这些函数首先会在底层将数据转化成列表,且元素会以堆的顺序排列

>>> nums
[1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> heap=list(nums)
>>> heapq.heapify(heap)
>>> heap
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
>>> heapq.heappop(heap)
-4
>>> heapq.heappop(heap)
1
>>> heapq.heappop(heap)
2
>>>

堆最重要的特性就是heap[0]总是最小那个的元素。此外,接下来的元素可依次通过heapq.heappop方法轻松找到。该方法会将第一个元素(最小的)弹出,然后以第二小的元素取而代之(这个操作的复杂度是O(logN),N代表堆的大小)

想找到最小或最大的元素(N=1时),那么用min()和max)会更加快。

>>> min(heap)
2
>>> max(heap)
42
>>> heap
[2, 7, 8, 23, 42, 37, 18, 23]
>>>

如果N和集合本身的大小差不多大,通常更快的方法是先对集合排序,然后做切片操作,使用sorted(items)[:N]或者sorted(items)[-N:])

实现优先级队列

我们想要实现一个队列,它能够以给定的优先级来对元素排序,且每次pop操作时都会返回优先级最高的那个元素。

heapq模块提供了堆排序算法的实现,heapq有两种方式创建堆,

  • 一种是使用一个空列表,然后使用heapq.heappush()函数把值加入堆中
  • 一种就是使用heap.heapify(list)转换列表成为堆结构
import heapq
class PriorityQueue:def __init__(self):self._queue = []self._index = 0def push(self, item, priority):# 把一个元组加入到队列里,元组包括优先级,索引,heapq.heappush(self._queue, (-priority, self._index, item))self._index += 1def pop(self):return heapq.heappop(self._queue)[-1]class Item:def __init__(self, name):self.name = namedef __repr__(self):#!r直接反应对象本体return 'Item({!r})'.format(self.name)q = PriorityQueue()
q.push(Item('foo'),1)
q.push(Item('bar'),5)
q.push(Item('spam'),4)
q.push(Item('grok'),1)
print(q.pop())
print(q.pop())
print(q.pop())

队列以元组(-priority,index,item)的形式组成。把priority取负值是为了让队列能够按元素的优先级从高到低的顺序排列。一般情况下是最小堆。

变量index的作用是为了将具有相同优先级的元素以适当的顺序排列。通过维护一个不断递增的索引,元素将以它们入队列时的顺序来排列。没有哪两个元组会有相同的index值(一旦比较操作的结果可以确定,Python就不会再去比较剩下的元组元素了)

如果想将这个队列用于线程间通信,还需要增加适当的锁和信号机制

在字典中将键映射到多个值上

我们想要一个能将键(key)映射到多个值的字典(即所谓的一键多值字典[multidict])

字典是一种关联容器,每个键都映射到一个单独的值上。如果想让键映射到多个值,需要将这多个值保存到另一个容器如列表或集合中。

为了能方便地创建这样的字典,可以利用collections模块中的defaultdict类defaultdict的一个特点就是它会自动初始化第一个值,这样只需关注添加元素即可。

defaultdict(<class 'list'>, {'a': [1, 2], 'b': [4]})
>>> from  collections import defaultdict
>>> d = defaultdict(list)
>>> d['a'].append(1)
>>> d['a'].append(2)
>>> d['b'].append(4)
>>> d
defaultdict(<class 'list'>, {'a': [1, 2], 'b': [4]})
>>> d= defaultdict(set)
>>> d['a'].add(1)
>>> d['a'].add(3)
>>> d['a'].add(3)
>>> d
defaultdict(<class 'set'>, {'a': {1, 3}})
>>>

用于分组,有一种java8里面Stream的味道

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

让字典保持有序

要控制字典中元素的顺序,可以使用collections模块中的OrderedDict类。当对字典做迭代时,它会严格按照元素初始添加的顺序进行。例如:

>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d['1']=1
>>> d['2']=2
>>> d['3']=3
>>> d
OrderedDict([('1', 1), ('2', 2), ('3', 3)])
>>>

当想构建一个映射结构以便稍后对其做序列化或编码成另一种格式时,OrderedDict就显得特别有用。例如,如果想在进行JSON编码时精确控制各字段的顺序,那么只要首先在OrderedDict中构建数据就可以了。

>>> import json
>>> json.dumps(d)
'{"1": 1, "2": 2, "3": 3}'
>>>

OrderedDict 内部维护了一个双向链表,它会根据元素加入的顺序来排列键的位置。第一个新加入的元素被放置在链表的末尾。接下来对已存在的键做重新赋值不会改变键的顺序

OrderedDict的大小是普通字典的2倍多,这是由于它额外创建的链表所致。因此,如果打算构建一个涉及大量OrderedDict实例的数据结构(例如从CSV文件中读取100000行内容到OrderedDict列表中),那么需要认真对应用做需求分析,是否可以用内存换便利

与字典有关的计算问题

我们想在字典上对数据执行各式各样的计算(比如求最小值、最大值、排序等)。

通常会利用zip()将字典的键和值反转过来

>>> prices={... 'ACME':45.23,
... 'AAPL':612.78,
... 'IBM1':205.55,
... 'HPQ':37.20,
... 'FB':10.75
... }
>>>
>>> min(zip(prices.values(),prices.keys()))
(10.75, 'FB')
>>> max(zip(prices.values(),prices.keys()))
(612.78, 'AAPL')
>>>

同样,要对数据排序只要使用zip()再配合sorted()就可以了

>>> sorted(zip(prices.values(),prices.keys()))
[(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM1'), (612.78, 'AAPL')]
>>>

zip()创建了一个迭代器,它的内容只能被消费一次,尝试对字典的值做计算。可以利用字典的values()方法来解决这个问题:但是对于K的获取并不方便。

在计算min()和max()时,如果碰巧value的值相同,则将返回拥有最小或最大key值的那个条目。

在两个字典中寻找相同点(交集)

有两个字典,我们想找出它们中间可能相同的地方(相同的键、相同的值等)。

关于字典的键有一个很少有人知道的特性,那就是它们也支持常见的集合操作,比如求并集、交集和差集。

如果需要对字典的键做常见的集合操作,那么就能直接使用keys-view对象而不必先将它们转化为集合。获取到迭代器,可以直接对字典进行运算交集差集

>>> a = {... 'x':1,
... 'y':2,
... 'z':3
... }
>>> b = {... 'w':10,
... 'x':11,
... 'y':2
... }
>>> a.keys() & b.keys()
{'y', 'x'}
>>> a.keys() - b.keys()
{'z'}

字典的items()方法返回由(key,value)对组成的items-view对象。这个对象支持类似的集合操作,可用来完成找出两个字典间有哪些键值对有相同之处的操作。

>>> a.items() & b.items()
{('y', 2)}
>>>

也可以对字典进行过滤。

>>> {key:a[key] for key in a.keys() - {'z','W'}}
{'y': 2, 'x': 1}
>>>

字典的values()方法并不支持集合操作。部分原因是因为在字典中键和值是不同的,从值的角度来看并不能保证所有的值都是唯一的。

从序列中移除重复项且保持元素间顺序不变

我们想去除序列中出现的重复元素,但仍然保持剩下的元素顺序不变。

如果序列中的值是可哈希(hashable)的,那么这个问题可以通过使用集合生成器轻松解决。

def dedupe(items):seen = set()for item in items:if item not in seen:yield item #生成有序列表seen.add(item)a = [1, 5, 2, 1, 9, 1, 5, 10]
print(list(dedupe(a)))
==========
[1, 5, 2, 9, 10]

如果没办法生成哈希值,如果一个对象是可哈希的,那么在它的生存期内必须是不可变的,它需要有一个hash()方法。

整数、浮点数、字符串、元组都是不可变的。


def dedupe(items, key=None):seen = set()for item in items:val = item if key is None  else  key(item)if val not in seen:yield itemseen.add(val)a=[{'x':1,'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
#转化为元组
print(list(dedupe(a,key=lambda b:(b['x'],b['y']))))

这里参数key的作用是指定一个函数用来将序列中的元素转换为可哈希的类型,这么做的目的是为了检测重复项。即行为参数化,lambda表达式的使用。

对切片命名

我们的代码已经变得无法阅读,到处都是硬编码的切片索引,我们想将它们清理干净

即通过对切片变量的定义,把可变的部分抽离出来。一般来说,内置的slice()函数会创建一个切片对象,可以用在任何允许进行切片操作的地方。

>>> recode='343534534532423435346547543534534534534564634534'
>>> int(recode[12:14]) * float(recode[10:13])
13608.0
>>> a = slice(12,14)
>>> b = slice(10,13)
>>> int(recode[a]) * float(recode[b])
13608.0

查看切片对象属性

>>> a.start
12
>>> a.stop
14
>>> a.step
>>>

通过使用indices(size)方法将切片映射到特定大小的序列上。

>>> s = "liruilong"
>>> a.indices(len(s))
(9, 9, 1)
>>>

找出序列中出现次数最多的元素

怎样找出一个序列中出现次数最多的元素呢?

collections.Counter 类就是专门为这类问题而设计的,它甚至有一个有用的most_common()方法直接给了你答案。

出现频率最高的 3 个单词

>>> words = [
... 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
... 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
... 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
... 'my', 'eyes', "you're", 'under'
... ]
>>> from collections import Counter
>>> word_counts = Counter(words)
>>> word_counts.most_common(3)
[('eyes', 8), ('the', 5), ('look', 4)]
>>> word_counts.most_common(1)
[('eyes', 8)]

作为输入,Counter 对象可以接受任意的hashable序列对象。在底层实现上,一个Counter对象就是一个字典,将元素映射到它出现的次数上

>>> word_counts
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
>>>

可以整合其他的容器来统计数据,下面为在原有基础上整合一个列表

>>> morewords = ['why','are','you','not','looking','in','my','eyes']
>>> for word in morewords:
...   word_counts[word] += 1
...
>>> word_counts
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})
>>>

当然,也可以通过update()方法,我们可以看到,update方法并没有替换原来的value,而是进行了累加,和字典的update方法有区别

>>> word_counts.update(morewords)
>>> word_counts
Counter({'eyes': 10, 'my': 5, 'the': 5, 'look': 4, 'into': 3, 'not': 3, 'around': 2, 'why': 2, 'are': 2, 'you': 2, 'looking': 2, 'in': 2, "don't": 1, "you're": 1, 'under': 1})
>>>

Counter生成的数据字典可以进行数据运算,只是针对相同Key的Value而言

>>> morewords = ['why','are','you','not','looking','in','my','eyes']
>>> words = [
... 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
... 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
... 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
... 'my', 'eyes', "you're", 'under'
... ]
>>> from collections import Counter
>>> Counter(words) + Counter(morewords)
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})
>>>

通过某个关键字排序一个字典列表

你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。

通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构。感觉和heapq模块中的函数nlargest()和nsmallest()有些类似

>>> rows = [
... {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
... {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
... {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
... {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
... ]
>>> from operator import itemgetter
>>> sorted(rows, key=itemgetter('fname'))
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]

rows 被传递给接受一个关键字参数的sorted()内置函数,参数是 callable 类型,从rows中接受一个单一元素,然后返回被用来排序的值,itemgetter() 函数就是负责创建这个 callable 对象的。

>>> sorted(rows, key=itemgetter('uid'))
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]

itemgetter() 函数也支持多个 keys,如果你传入多个索引参数给 itemgetter() ,它生成的 callable 对象会返回一个包含所有元素值的元组,并且 sorted() 函数会根据这个元组中元素顺序去排序比如下面的代码

>>> sorted(rows, key=itemgetter('lname','fname'))
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
>>>

itemgetter() 有时候也可以用 lambda 表达式代替

>>> sorted(rows, key=lambda r: r['fname'])
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
>>> sorted(rows, key=lambda r: (r['lname'],r['fname']))
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
>>>

使用 itemgetter() 方式会运行的稍微快点。同样适用于 min() 和 max() 等函数

>>> import  heapq
>>> heapq.nsmallest(2,rows,key=itemgetter('fname'))
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
>>> min(rows,key=itemgetter('fname'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
>>>

排序不支持原生比较的对象

你想排序类型相同的对象,但是他们不支持原生的比较操作

内置的 sorted() 函数有一个关键字参数 key ,可以传入一个 callable 对象给它,这个 callable 对象对每个传入的对象返回一个值,这个值会被 sorted 用来排序

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   user.py
@Time    :   2022/04/29 19:35:33
@Author  :   Li Ruilong
@Version :   1.0
@Contact :   1224965096@qq.com
@Desc    :   None
"""# here put the import libclass User:def __init__(self, user_id):self.user_id = user_iddef __repr__(self):return 'User({})'.format(self.user_id)def sort_notcompare():users = [User(23), User(3), User(99)]print(users)print(sorted(users, key=lambda u: u.user_id))if __name__ == "__main__" : sort_notcompare()
============
[User(23), User(3), User(99)]
[User(3), User(23), User(99)]

另外一种方式是使用 operator.attrgetter() 来代替 lambda 函数:

   from operator import attrgetterprint(sorted(users, key=attrgetter('user_id')))

attrgetter() 函数通常会运行的快点,并且还能同时允许多个字段进行比较

sorted(users, key=attrgetter('last_name', 'first_name'))

跟 operator.itemgetter() 函数作用于字典类型很类似 ,同样适用于像 min() 和 max() 之类

通过某个字段将记录分组

你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭代访问。

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'},
... ]
>>> from operator import itemgetter
>>> from itertools import groupby
>>> rows.sort(key=itemgetter('date'))
>>> 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()仅仅检查连续的元素

如果你仅仅只是想根据date字段将数据分组到一个大的数据结构中去,并且允许随机访问,最好使用defaultdict()来构建一个多值字典

>>> from collections import defaultdict
>>> rows_by_date = defaultdict(list)
>>> rows = [
N CLARK', 'date': '07/03/... {'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'},
... ]
>>> for row in rows:
...     rows_by_date[row['date']].append(row)
...
>>> rows_by_date
defaultdict(<class 'list'>,
{'07/01/2012': [{'address': '5412 N CLARK', 'date': '07/01/2012'},{'address': '4801 N BROADWAY', 'date': '07/01/2012'}], '07/04/2012': [{'address': '5148 N CLARK', 'date': '07/04/2012'},{'address': '1039 W GRANVILLE', 'date': '07/04/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'}]
})
>>>

过滤序列元素

你有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列

最简单的过滤序列元素的方法就是使用列表推导

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n > 0]
[1, 4, 10, 2, 3]
>>> [n for n in mylist if n < 0]
[-5, -7, -1]
>>>

潜在缺陷就是如果输入非常大的时候会产生一个非常大的结果集,占用大量内存,可以用生成器表达式迭代产生过滤的元素。

>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x7f3a14e538e0>
>>> for i in pos:
...     print(i)
...
1
4
10
2
3
>>>

过滤规则比较复杂,将过滤代码放到一个函数中,然后使用内建的filter()函数

values = ['1', '2', '-3', '-', '4', 'N/A', '5']
def is_int(val):try:x = int(val)return Trueexcept ValueError:return False
# filter() 函数创建了一个迭代器
ivals = list(filter(is_int, values))
print(ivals)

在过滤的时候转换数据

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> import math
>>> [math.sqrt(n) for n in mylist if n > 0]
[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
>>>

过滤操作的一个变种就是将不符合条件的值用新的值代替,

>>> [n if n > 0 else 0 for n in mylist]
[1, 4, 0, 10, 0, 2, 3, 0]
>>> [n if n < 0 else 0 for n in mylist]
[0, 0, -5, 0, -7, 0, 0, -1]
>>>

itertools.compress(),它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数.然后输出 iterable 对象中对应选择器为 True 的元素当你需要用另外一个相关联的序列来过滤某个序列的时候,这个函数是非常有用的

>>> addresses = [
... '5412 N CLARK',
... '5148 N CLARK',
... '5800 E 58TH',
... '2122 N CLARK'
... '5645 N RAVENSWOOD',
... '1060 W ADDISON',
... '4801 N BROADWAY',
... '1039 W GRANVILLE',
... ]
>>> counts = [ 0, 3, 10, 4, 1, 7, 6, 1]
>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
['5800 E 58TH', '4801 N BROADWAY', '1039 W GRANVILLE']
>>>

compress() 也是返回的一个迭代器

从字典中提取子集

你想构造一个字典,它是另外一个字典的子集

是使用字典推导

>>> prices = {... 'ACME': 45.23,
... 'AAPL': 612.78,
... 'IBM': 205.55,
... 'HPQ': 37.20,
... 'FB': 10.75
... }
>>> {key: value for key, value in prices.items() if value > 200}
{'AAPL': 612.78, 'IBM': 205.55}
>>> tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
>>> {key: value for key, value in prices.items() if key in tech_names}
{'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
>>>

字典推导能做到的,通过创建一个元组序列然后把它传给 dict()也能实现,字典推导方式表意更清晰,并且实际上也会运行的更快些

dict((key, value) for key, value in prices.items() if value > 200)

将名称映射到序列的元素中

你有一段通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的代码难以阅读,于是你想通过名称来访问元素。

collections.namedtuple() 函数通过使用一个普通的元组对象来帮你解决这个问题。

>>> from collections import namedtuple
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('jonesy@example.com', '2012-10-19')
>>> sub
Subscriber(addr='jonesy@example.com', joined='2012-10-19')

namedtuple() 返回 Python 中标准元组类型子类的一个工厂方法,传递一个类型名和你需要的字段给它,然后它就会返回一个类,虽然看起来像一个类实例,但是是可交换的。

>>> sub.addr
'jonesy@example.com'
>>> sub[1]
'2012-10-19'
>>> addr, joined = sub
>>> addr
'jonesy@example.com'
>>>

命名元组的一个主要用途是将你的代码从下标操作中解脱出来,数据查询中返回很大一个元组,通过下标去操作其中的元素有很多可变性,如果使用元组命名则不用考虑

def compute_cost(records):total = 0.0for rec in records:total += rec[1] * rec[2]return totalfrom collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price'])
def compute_cost(records):total = 0.0for rec in records:s = Stock(*rec)total += s.shares * s.pricereturn total

命名元组另一个用途就是作为字典的替代,字典存储需要更多的内存空间

需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效

是需要注意一个命名元组是不可更改的.如果你真的需要改变后的属性,那么可以使用命名元组实例的replace()方法

>>> sub
Subscriber(addr='jonesy@example.com', joined='2012-10-19')
>>> sub.addr
'jonesy@example.com'
>>> sub.addr = '1@qq.com'
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> sub = sub._replace(addr='1@qq.com')
>>> sub
Subscriber(addr='1@qq.com', joined='2012-10-19')
>>>

它会创建一个全新的命名元组并将对应的字段用新的值取代

_replace() 方法还有一个很有用的特性就是当你的命名元组拥有可选或者缺失字段时候,它是一个非常方便的填充数据的方法。你可以先创建一个包含缺省值的原型元组,然后使用 _replace() 方法创建新的值被更新过的实例,类似于类实例的初始化调用构造函数

>>> from collections import namedtuple
>>> Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
>>> stock_prototype = Stock('', 0, 0.0, None, None)
>>> def dict_to_stock(s):
...     return stock_prototype._replace(**s)
...
>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> dict_to_stock(a)
Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
>>>

如果你的目标是定义一个需要更新很多实例属性的高效数据结构,那么命名元组并不是你的最佳选择。这时候你应该考虑定义一个包含 slots 方法的类

同时对数据做转换和换算

我们需要调用一个换算(reduction)函数(例如sumO、min)、max)),但首先得对数据做转换或筛选。

>>> num = [1,2,3,4,5]
>>> sum(x * x for x in num)
55
>>>

将多个映射合并为单个映射

我们有多个字典或映射,想在逻辑上将它们合并为一个单独的映射结构,以此执行某些特定的操作,比如查找值或检查键是否存在。

一种简单的万法是利用collections模块中的ChainMap类来解决

>>> a={'x':1,'z':3}
>>> b={'y':2,'z':4}
>>> from collections import ChainMap
>>> ChainMap(a,b)
ChainMap({'x': 1, 'z': 3}, {'y': 2, 'z': 4})
>>>

ChainMap可接受多个映射然后在逻辑上使它们表现为一个单独的映射结构。但是,这些映射在字面上并不会合并在一起。相反,ChainMap只是简单地维护一个记录底层映射关系的列表,然后重定义常见的字典操作来扫描这个列表。大部分的操作都能正常工作。

>>> len(ChainMap(a,b))
3
>>> list(ChainMap(a,b).keys())
['y', 'z', 'x']
>>> list(ChainMap(a,b).values())
[2, 3, 1]
>>>

如果有重复的键,那么这里会采用第一个映射中所对应的值。修改映射的操作总是会作用在列出的第一个映射结构上。

ChainMap与带有作用域的值,比如编程语言中的变量(即全局变量、局部变量等)一起工作时特别有用。

>>> v = ChainMap()
>>> v['x'] = 1
>>> v = v.new_child()
>>> v['x'] = 2
>>> v = v.new_child()
>>> v['x'] = 3
>>> v
ChainMap({'x': 3}, {'x': 2}, {'x': 1})
>>> v['x']
3
>>> v = v.parents
>>> v['x']...............
2
>>> v = v.parents
>>> v['x']
1
>>> v
ChainMap({'x': 1})
>>>

作为ChainMap的替代方案,我们可能会考虑利用字典的update()方法将多个字典合并在一起。但是破坏了原始的数据结构,而ChainMap使用的就是原始的字典,因此它不会产生这种令人不悦的行为。

>>> a
{'x': 1, 'z': 3}
>>> b
{'y': 2, 'z': 4}
>>>
>>> c= dict(b)
>>> c.update(a)
>>> c
{'y': 2, 'z': 3, 'x': 1}

Python实战之数据结构和算法相关推荐

  1. Python–cookbook–1.数据结构与算法

    Python–cookbook–1.数据结构与算法 文章目录 Python–cookbook–1.数据结构与算法 解压序列赋值给多个变量 解压可迭代对象赋值给多个变量 文件对比,对比当前行和之前行 查 ...

  2. python define graph_Python数据结构与算法之图结构(Graph)实例分析

    本文实例讲述了Python数据结构与算法之图结构(Graph).分享给大家供大家参考,具体如下: 图结构(Graph)--算法学中最强大的框架之一.树结构只是图的一种特殊情况. 如果我们可将自己的工作 ...

  3. 数据结构与算法python描述_数据结构与算法——Python语言描述.pdf

    数据结构与算法--Python语言描述.pdf 欢迎加入非盈利Python编学习交流程QQ群783462347,群里免费提供500+本Python书籍! 欢迎加入非盈利Python编程学习交流程QQ群 ...

  4. python 栈_Python数据结构与算法05:基本结构:栈的应用之括号匹配

    注:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性. 本文阅读时间约为7分钟. 算法流程 括号匹配情况介绍 我们经常会遇到类似(9-1)*(8+7)/(4-2).print(& ...

  5. Python编程之数据结构与算法练习_004

    Some排序算法的Python实现.不废话写原理,直接撸代码. 1.Bubble sort 冒泡排序 import random import copymaxSize = 10000 maxValue ...

  6. python实现队列_用Python实现的数据结构与算法:队列

    一.概述 队列(Queue)是一种先进先出(FIFO)的线性数据结构,插入操作在队尾(rear)进行,删除操作在队首(front)进行. 二.ADT 队列ADT(抽象数据类型)一般提供以下接口: Qu ...

  7. python 下的数据结构与算法---6:6大排序算法

    顶先最后推荐:哈哈,意思是放到顶部强调其重要性,但是应该我总结的六种算法看完了后再看的一篇醍醐灌顶的文章 一:冒泡排序(Bubble Sort) 原理:假设有n个数,第一轮时:从第一个元素开始,与相邻 ...

  8. python什么是数据结构_python算法与数据结构-数据结构介绍(38)

    一.什么是数据结构 数据结构,直白地理解,就是研究数据的存储方式. 我们知道,数据存储只有一个目的,即为了方便后期对数据的再利用,就如同我们使用数组存储 {1,2,3,4,5} 是为了后期取得它们的值 ...

  9. wps两列数据分别作为xy轴_【python实战】用KNN算法实现数据分类和数据预测

    导语:数据挖掘,又译为数据采矿,是指从大量的数据中通过算法搜索隐藏于其中信息的过程.本篇内容主要向大家讲述如何使用KNN算法进行数据分类和数据预测. 1.数据分类基础概念 数据分类就是相同内容.相同性 ...

最新文章

  1. Nature Plants:根系微生物可以远程提高植物应对地上部环境胁迫的能力
  2. Git之深入解析如何通过GPG签署和验证工作
  3. mysql用supervisor管理_使用Supervisor管理进程
  4. lineNumber: 1; columnNumber: 1; 前言中不允许有内容
  5. 关于Django中的数据库操作API之distinct去重的一个误传
  6. 一文带你掌握抓包工具的使用-科来
  7. 方舟生存进化手游服务器显示以驯养两千,《方舟:生存进化》手游官方网站-大型生存探索沙盒手游...
  8. 手机号码校验(手机号---座机号)
  9. Calibre电子书简繁转换
  10. js 获取字符串的UTF8编码
  11. C#中判断空字符串的3种方法性能分析 1
  12. 数据预处理:分类和顺序变量转化为数值型数据
  13. English Learning - L2 语音作业打卡 辅音舌齿龈音 [t] [d] Day24 2023.3.16 周四
  14. 支持向量机识别数字集(数据采集+模型训练+预测输出)
  15. pymol配体平移与旋转
  16. Boolean.TRUE 和 true 性能对比
  17. MATLAB方程式求解
  18. Windows 使用键盘移动窗口
  19. 网易月薪25K的Linux运维面试真题曝光
  20. 分享冲压模具设计与制造的冲压模具材料

热门文章

  1. 计算机access的应用,Access 编程简介
  2. mysql 同步 es_mysql数据实时同步到Elasticsearch
  3. 意外的服务器响应IE,使用selenium python时,IE驱动程序服务器出现意外的关闭错误...
  4. SQL语句 with as 用法
  5. python基于PHP+MySQL的大学生宿舍管理系统
  6. 关于计算机知识的名言警句,关于知识的18个名言警句!
  7. java面试题(高)
  8. 警惕手机病毒成偷跑流量“陷阱”
  9. 在飘飞的春雨中,书写那空洞的落寞
  10. SMT贴片加工流程和注意事项