1. python数据结构的奇淫巧技
  2. 如何将序列进行分解

如果有一个包含n个元素的队列或者集合,如何解压并且赋值给n个变量呢?

可以直接使用变量进行承接

比如

>>> p = (4, 5)

>>> x, y = p

如果想要获取某些下标的数据而抛弃其他的,可以使用占位符去占位

>>> _,x = p

如果想要将可迭代对象中的多个对象解压出来,我们可以使用 星号表达式

比如我们希望获取到一个列表中中间的数据,也就是去掉第一个和最后一个的数据,那么我们就可以使用 *了

def drop_first_last(grades):

first, *middle, last = grades

return avg(middle)

需要注意,这个*表达式后面的变量是一个列表,即使没有,也是一个数量为0的列表

这样的迭代语法可以用于任何可迭代的对象

甚至是字符串这种对象

>>> s = ‘Hello’

>>> a, b, c, d, e = s

不过星号表达式更常见的使用常见是用于函数上,表明可变参数

  1. Deque,保存最后几个元素

比如我们想要保存最后几个元素,那么可以使用如下的代码

def search(lines, pattern, history=5):

previous_lines = deque(maxlen=history)

for line in lines:

if pattern in line:

yield line, previous_lines

previous_lines.append(line)

这里我们使用deque创建了一个固定大小的队列,当新的元素加入并且这个队列已经满的话,最老的元素就会被移除掉,如果我们不设置队列大小,就会得到一个无限大小的队列

从deque中可以分别从两端添加或者弹出元素

d.appendleft(4)

d.append(1)

d.pop()

d.popleft()

  1. 获取到最大或者最小的几个元素

简单的做法,可以是sort之后进行列表的截取[:]

而在heapq模块,则是有着函数nlargest()和nsmallest()解决这个问题

import heapq

nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]

print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]

这个函数还支持传入一个关键字参数来从传入参数中进行提取字段比较

portfolio = [

{‘name’: ‘IBM’, ‘shares’: 100, ‘price’: 91.1},

{‘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}

]

cheap = heapq.nsmallest(3,portfolio,key=lambda s: s[‘price’])

其底层是利用了一个有序的列表实现的,然后提取了N个最大或者最小的元素

而这个队列的有序性是利用了堆排序实现的

如果我们想要获取使用堆排序,则可以使用heapq.heapify(heap)

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

>>> import heapq

>>> heap = list(nums)

>>> heapq.heapify(heap)

>>> heap

[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]

这样我们还可以使用heappop提取元素,不断的提取下标最小的元素

利用heappop,我们还可以实现一个带有优先级的队列

首先我们先说其中重点的函数heapq.heappush

其可以指定优先级的方式压入元素到一个队列中

class PriorityQueue:

def __init__(self):

self._queue = []

self._index = 0

def push(self, item, priority):

heapq.heappush(self._queue, (-priority, self._index, item))

self._index += 1

def pop(self):

return heapq.heappop(self._queue)[-1]

这样我们利用heapq.heappop来返回最小的元素

但是在插入的时候,我们利用了heappush函数,这个参数我们在压入数据的时候压入了一个元组,heappush会使用这个元组进行优先级的比较,所以我们需要确保不能有一样的元组优先级,所以我们先传入了优先级,后传入了index,确保了在优先级一样的情况下,还能利用index机性能比较

  1. 字典的值为列表

如果我们想要创建一个如下的值为列表的字典,可以这样构建

d = {

‘a’ : [1, 2, 3],

‘b’ : [4, 5]

}

e = {

‘a’ : {1, 2, 3},

‘b’ : {4, 5}

}

分别创建了列表和集合

而如果我们想要创建一个默认值为迭代器的字典,则可以使用collections模块的中的defaultdict来构建这样的字典,比如‘

d = defaultdict(list)

d[‘a’].append(1)

d[‘a’].append(2)

d[‘b’].append(4)

d = defaultdict(set)

d[‘a’].add(1)

d[‘a’].add(2)

d[‘b’].add(4)

这样我们会自动的创建为列表或者集合

让我们可以更为简单的使用if not in来判断下

不过代码不如defaultdict简洁

  1. 字典保持插入顺序

如果是字典能够保证插入顺序,则可以使用collections模块的OrderDict类,可以保持元素的插入顺序

d = OrderedDict()

d[‘foo’] = 1

d[‘bar’] = 2

d[‘spam’] = 3

d[‘grok’] = 4

这样的字典如下

# Outputs “foo 1”, “bar 2”, “spam 3”, “grok 4”

这个字典很适合在空值JSON编码字段顺序的时候使用,可以明确一个固定顺序返回

  1. 字典列表的排序

如果我们想要对一个字典的列表进行排序,可以使用operator的itermgetter函数,

比如一个列表如下

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}

]

排序就是

rows_by_fname = sorted(rows, key=itemgetter(‘fname’))

[{‘fname’: ‘Big’, ‘uid’: 1004, ‘lname’: ‘Jones’},

{‘fname’: ‘Brian’, ‘uid’: 1003, ‘lname’: ‘Jones’},

{‘fname’: ‘David’, ‘uid’: 1002, ‘lname’: ‘Beazley’},

{‘fname’: ‘John’, ‘uid’: 1001, ‘lname’: ‘Cleese’}]

Sorted本质上需要创建从传入的row中获取一个单一元素,从而进行排序,itemgetter函数就是创建这个callable的itemgettter可以传入多个字段进行排序

当然支持使用lambda表达式进行替代

rows_by_lfname = sorted(rows, key=lambda r: (r[‘lname’],r[‘fname’]))

那么我们就可以使用lambda来支持一些没有实现原生比较的对象

class User:

def __init__(self, user_id):

self.user_id = user_id

def __repr__(self):

return ‘User({})’.format(self.user_id)

Sort(users, key=lambda u:u.user_id)

或者是使用operator.attrgetter()来代替lambda函数

>>> from operator import attrgetter

>>> sorted(users, key=attrgetter(‘user_id’))

如果利用attrgetter函数,支持多个字段进行比较

  1. 在字典中的运算

如果我们想要对值进行计算,可以考虑先使用zip函数将两者反转过来

比如

min_price = min(zip(prices.values(), prices.keys()))

# min_price is (10.75, ‘FB’)

max_price = max(zip(prices.values(), prices.keys()))

需要注意的是zip函数是一个只能访问一次的迭代器,如果访问多次就会产生错误

而且字典本身是一个key集合和value集合的映射关系,其中keys可以返回一个包含所有键信息的视图,其本身支持集合操作,比如获取并集,交集,差集

同样items也是,我们来看下

a = {

‘x’ : 1,

‘y’ : 2,

‘z’ : 3

}

b = {

‘w’ : 10,

‘x’ : 11,

‘y’ : 2

}

keys() & b.keys() # { ‘x’, ‘y’ }

a.keys() – b.keys() # { ‘z’ }

a.items() & b.items() # { (‘y’, 2) }

因此完全没必要去转换为集合,而是直接使用就可以了

  1. 删除序列中相同元素

如果是hashable的,那么很简单就可以

def dedupe(items):

seen = set()

for item in items:

if item not in seen:

yield item

seen.add(item)、

如果不是,则可以通过函数来做到

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 item

seen.add(val)

利用传入一个key函数来转换为hashable的

但如果就是想简单的创建一个不包含重复元素的集合,则可以如下

>>> set(a)

{1, 2, 10, 5, 9}

  1. 切片的使用

切片可以优化我们对迭代器的截取

比如我们有一个字符串

record = ‘………………..100 …….513.25 ……….’

那么可以使用Slice进行切片

SHARES = slice(20, 23)

PRICE = slice(31, 37)

cost = int(record[SHARES]) * float(record[PRICE])

对于这个Slice,我们可以在很多地方以很多形式去使用Slice

>>> items = [0, 1, 2, 3, 4, 5, 6]

>>> a = slice(2, 4)

>>> items[2:4]

[2, 3]

>>> items[a]

[2, 3]

>>> items[a] = [10,11]

>>> items

[0, 1, 10, 11, 4, 5, 6]

>>> del items[a]

>>> items

[0, 1, 4, 5, 6]

而且如果对于即将应用的序列大小不知道的话,可以使用indices(size)进行缩放

从而适应对应的边界,比如

>>> a= slice(5,50,2)

>>> s = ‘HelloWorld’

>>> a.indices(len(s))

  1. 统计出现次数

collections.Counter专门为此而生。内部的most_common方法可以计算哪些频率最高

假设我们有一个列表

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)

# 出现频率最高的3个单词

top_three = word_counts.most_common(3)

print(top_three)

Counter对象可以接受任何可哈希的元素序列,本质上就是一个字典,从而管理元素的出现次数

>>> word_counts[‘not’]

1

不过其封装了很多好使的函数

>>> morewords = [‘why’,’are’,’you’,’not’,’looking’,’in’,’my’,’eyes’]

>>> word_counts.update(morewords)

而且还可以使用数学计算

>>> a = Counter(words)

>>> b = Counter(morewords)

>>> c = a + b

  1. 记录分组

itertools.groupby()函数支持数据分组,不过这个函数的前提是要求,数组已经在分组前对分组的key进行了排序

比如如下的代码

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′},

]

首先是进行排序

rows.sort(key=itemgetter(‘date’))

然后利用groupby进行分组

from itertools import groupby

for date, items in groupby(rows, key=itemgetter(‘date’)):

for i in items:

print(‘ ‘, i)

这个函数要求必须是排序之后的,因为其只会检查相邻的数据

如果有类似的结构,可以考虑使用defaultdict来创建一个多值的字段

  1. Filter函数

如果我们希望从序列中过滤或者提取一些值的话ƒ

我们就可以使用列表进行推导完成

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]

>>> [n for n in mylist if n > 0]

但是列表推导的问题在于会占用大量的内存,而且过滤规则比较复杂的时候不好实现,则时候就可以考虑使用filter函数

def is_int(val):

try:

x = int(val)

return True

except ValueError:

return False

values = [‘1’, ‘2’, ‘-3’, ‘-‘, ‘4’, ‘N/A’, ‘5’]

ivals = list(filter(is_int, values))

需要注意的是filter函数创建了一个迭代器,需要我们将其转换为list或者set

我们这里多说一点,我们可以使用列表推导进行数据转换

>>> clip_neg = [n if n > 0 else 0 for n in mylist]

我们同样可以使用推导创建一个子字典

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}

或者使用dict函数来实现

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

  1. 命名元组

collections.namedtuple()支持创建一个命名元组

从而以一个近似类的方式进行使用

>>> Subscriber = namedtuple(‘Subscriber’, [‘addr’, ‘joined’])

这样我们就可以直接使用这个Subscriber了

>>> sub = Subscriber(‘jonesy@example.com’, ‘2012-10-19’)

Sub.addr

而且其支持元组的相关功能

但是只能作为一个类的替代品,因为其改变属性需要使用_replace() 方法

从而创建出一个新的元组来代替来的元组

>>> s = s._replace(shares=75)

还是不太方便的

如果是由很多实例属性需要更新的话,那么应该考虑创建一个类,而非元组

  1. 合并字典

有两种方式,分别需要我们创建新的字典对象和包装原本的字典对象、

如果是使用chainmap的话

c = ChainMap(a,b)

其接受多个字典并在内部创建一个列表容纳这些字典

支持大部分的操作,但是由于列表具有顺序,所以经常返回第一个第一个出现值,同样修改会修改第一个存在的值

或者使用dict的update操作

>>> a = {‘x’: 1, ‘z’: 3 }

>>> b = {‘y’: 2, ‘z’: 4 }

>>> merged = dict(b)

>>> merged.update(a)

这样会创建一个新的字典对象,并且会破坏原有的结构

发表评论

邮箱地址不会被公开。 必填项已用*标注