【Python Cookbook】第一章 数据结构与算法


一、元组或序列的小技巧

1.1 序列分解

list1 = [2, 'apple', (1,2,3), [4,4]]
num, *mid, _ = list1
print(num)
2
print(mid)
['apple', (1, 2, 3)]

从上面的代码输出可以看到:

  1. num被赋予了第1个位置的值;
  2. 得益于*这个符号,mid分别被赋予了从第2个位置开始到倒数第1个之前的值;
  3. _表示丢弃当前位置的值。,

当然这也适用于字符串(String)与元组(Tuple),例如:

string1 = 'Hello world!'
first, *_, final = string1
print(first)
H
print(final)
!

上述的代码中需要注意的,*这个符号,可以与_搭配使用,其将中间的位置值全部丢弃了。


1.2 序列去重

如果只是简单地去除重复项,可以使用set,如下:

a = [1, 5, 2, 1, 2, 3]
set(a)
{
    
    1, 2, 3, 5}

但上述的方法存在缺陷,不能保证原本的元素间顺序不变。

因此我们可以这么解决,如果序列中的值是可哈希的(hashable),如下:

如果一个对象是可哈希的,那么它是不可变的,需要有一个__hash__()方法,例如整数、浮点数、字符串、元组是不可变的。

def dedupe(items):
	seen = set()
	for item in items:
		if item not in seen:
			yield item			
			# 首先,将yield当做return来看,也就是说这个函数返回的是item;
			# 正常情况下,yield会使得代码终止至此,使用next()才能进行下一步,而在 for 循环中会自动调用 next();
			# for循环之后已经出现了很多的item,也就是说item可迭代了。
			seen.add(item)
            
a = [1, 5, 2, 1, 2, 3]
print(list(dedupe(a)))
[1, 5, 2, 3]

那么如果序列中的元素不可哈希的,该如何做呢?代码如下:

def dedupe(items, key):
	seen = set()
	for item in items:
		val = item if key is None else key(item)	# 通过key提取了不可哈希中可哈希的值
		if val not in seen:
			yield item
			seen.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 d: (d['x'], d['y']))))
[{
    
    'x': 1, 'y': 2}, {
    
    'x': 1, 'y': 3}, {
    
    'x': 2, 'y': 4}]

1.3 序列中次数最多元素

可以使用collection包来解决找到序列中出现次数最多的元素,如下:

words = ['a', 'dog', 'a', 'people', 'like', 'see', 'you', 'people', 'dog', 'a']

from collections import Counter
word_count = Counter(words)
top_three = word_count.most_common(3)
print(top_three)
[('a', 3), ('dog', 2), ('people', 2)]

在上面的基础上,可以调用update()方法增加新的words,如下:

more_words = ['like', 'see', 'you']
word_count.update(more_words)

当然,可以与数学运算符号结合使用,如下:

a = Counter(words)
b = Counter(more_words)

print(a + b)	# Combinne
Counter({
    
    'a': 3, 'dog': 2, 'people': 2, 'like': 2, 'see': 2, 'you': 2})
print(a - b)	# Subtract
Counter({
    
    'a': 3, 'dog': 2, 'people': 2})

1.4 序列中筛选元素

筛选序列中的元素,最简单的办法是列表推导式(list comprehension),如下:

list1 = [1, 3, 4, -5, 0, 8, -3]
[n for n in list1 if n > 0]
[1, 3, 4, 8]

(n for n in list1 if n < 0)
<generator object <genexpr> at 0x000002575B79DC10>

for i in (n for n in list1 if n < 0):
    print(i)
-5
-3

甚至可以有更复杂的情形,如下:

list1 = [1, 3, 4, -5, 0, 8, -3]
import math
[math.sqrt(n) if n > 0 else 0 for n in list1]
[1.0, 1.7320508075688772, 2.0, 0, 0, 2.8284271247461903, 0]

当然还可以使用内建的filter()函数进行处理,(由于filter()方法生成的是一个迭代器,因此我们需要添加list()使其成为一个可视化的列表类型),如下:

list1 = ['1', '-2', '-', 'N/A']

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False

ivals = list(filter(is_int, list1))
print(ivals)
['1', '-2']

1.5 序列数据转换与换算

首先,优雅地对一个序列求平方和,如下:

list1 = [1, 5, 2, 1, 2, 3]
sum(x * x for x in list1)
44

当然可以更高级地,我们以字典列表为例子,如下:

prices = [
    {
    
    'name':'banana', 'price':15, 'num': 3},
    {
    
    'name':'apple', 'price':10, 'num': 2},
]

print(sum(s['price'] * s['num'] for s in prices))
65

二、字典的小技巧

2.1 字典的最值与排序

求一个字典中的最大值与最小值,一般会使用zip()将字典中的keyvalue反转过来,如下:

prices = {
    
    
    'banana': 16,
    'apple': 11,
    'meat': 30,
    'fish': 23
}
min_price = min(zip(prices.values(), prices.keys()))
min_price
(11, 'apple')

max_price = max(zip(prices.values(), prices.keys()))
max_price
(30, 'meat')

若存在条目拥有相同的value值,那么在比较大小时,将对key值进行比较,如下:

prices = {
    
    
    'banana': 11,
    'apple': 11,
}
min(zip(prices.values(), prices.keys()))
(11, 'apple')

max(zip(prices.values(), prices.keys()))
(11, 'banana')

当然一般任务中,我们只需要知道最小values值的key是什么,如下:

prices = {
    
    
    'banana': 16,
    'apple': 11,
    'meat': 30,
    'fish': 23
}
min(prices, key=lambda k: prices[k])
'apple'

max(prices, key=lambda k: prices[k])
'meat'

下面是排序,排序要配合使用zip()sorted(),如下:

price_sorted = sorted(zip(prices.values(), prices.keys()))
price_sorted
[(11, 'apple'), (16, 'banana'), (23, 'fish'), (30, 'meat')]

但需要注意,zip创建的迭代器,内容只会被消费一次,例如下面就会报错:

price_temp = zip(prices.values(), prices.keys())
print(min(price_temp))
(11, 'apple')

print(max(price_temp))
ValueError: max() arg is an empty sequence

因为zip中的内容在第一次调用的时候就被消耗掉了。


2.2 字典间的相同点

可以使用&来寻找字典中的相同点,使用-来寻找不同点,我们先设计两个字典如下:

a = {
    
    
    'x': 11,
    'y': 22,
    'z': 33,
}

b = {
    
    
    'v': 1,
    'x': 2,
    'y': 22,
}

寻找a字典与b字典中相同的keys,如下:

print(a.keys() & b.keys())
{
    
    'y', 'x'}

寻找b字典中不存在,但a字典中存在的keys,如下:

print(a.keys() - b.keys())
{
    
    'z'}

寻找a字典与b字典中相同的items,如下:

print(a.items() & b.items())
{
    
    ('y', 22)}

2.3 通过公共键对字典列表排序

rows = [
    {
    
    'name':'wang', 'age':15},
    {
    
    'name':'ye', 'age':19},
    {
    
    'name':'dong', 'age':16},
    {
    
    'name':'mao', 'age':14}
]

rows_by_age = sorted(rows, key=lambda r: r['age'])
print(rows_by_age)
[{
    
    'name': 'mao', 'age': 14}, 
 {
    
    'name': 'wang', 'age': 15}, 
 {
    
    'name': 'dong', 'age': 16}, 
 {
    
    'name': 'ye', 'age': 19}]

另外的,可以通过调用operator包下的itemgetter方法来使用,如下:

from operator import itemgetter
rows_by_age = sorted(rows, key=itemgetter('age'))
print(rows_by_age)
[{
    
    'name': 'mao', 'age': 14}, 
 {
    
    'name': 'wang', 'age': 15}, 
 {
    
    'name': 'dong', 'age': 16}, 
 {
    
    'name': 'ye', 'age': 19}]

同样的,上述方法可以作用于min()max()这类函数,如下:

min(rows, key=itemgetter('age'))
{
    
    'name': 'mao', 'age': 14}

可以看到,lambad表达式是一个十分有效的方法,在对于不原生支持排序的对象比较时候,应调用operator.attrgetter()方法,不但可以对单变量排序,也可以同时选择多变量进行排序。


2.4 根据字段将记录分组

可以先按某关键词对该序列进行排序,然后通过调用itertools.groupby()方法进行分组,如下:

rows = [
    {
    
    'name':'wang', 'age':14},
    {
    
    'name':'ye', 'age':19},
    {
    
    'name':'dong', 'age':16},
    {
    
    'name':'mao', 'age':14},
    {
    
    'name':'hu', 'age': 16}
]
from operator import itemgetter
from itertools import groupby

rows.sort(key=itemgetter('age'))

for age, items in groupby(rows, key=itemgetter('age')):
    print(age)
    for i in items:
        print(' ', i)
14
  {
    
    'name': 'wang', 'age': 14}
  {
    
    'name': 'mao', 'age': 14}
16
  {
    
    'name': 'dong', 'age': 16}
  {
    
    'name': 'hu', 'age': 16}
19
  {
    
    'name': 'ye', 'age': 19}

2.5 字典提取子集

我们想创建一个小字典,其本身是另一个字典的子集,如下:

prices = {
    
    
    'banana': 16,
    'apple': 11,
    'meat': 30,
    'fish': 23
}
{
    
    key:value for key, value in prices.items() if value > 20}
{
    
    'meat': 30, 'fish': 23}

tmp_name = ['meat', 'apple']
{
    
    key:value for key, value in prices.items() if key in tmp_name}
{
    
    'apple': 11, 'meat': 30}

2.6 字典的替代 # 命名元组namedtuple

相对于字典,使用namedtuple在大型数据结构中会更加高效,基础使用如下:

from collections import namedtuple

FRUIT = namedtuple('Fruit', ('name', 'price'))
sub = FRUIT('banana', 16)

print(sub.name, sub.price)
banana 16

也可以像元祖元组一样去得到里面的值,如下:

name, price = sub
print(name, price)
banana 16

如果要对其中的值进行改变,可以使用namedtuple实例_replace()方法来实现,如下:

from collections import namedtuple
FRUIT = namedtuple('Fruit', ('name', 'price'))
sub = FRUIT('banana', 16)

sub._replace(price=20)
Fruit(name='banana', price=20)

_replace()方法还有另一个用途,可以用来填充缺失数据的命名元组,需要先建立一个包含默认值的“原型”元组,然后再用该方法,如下:

from collections import namedtuple
FRUIT = namedtuple('Fruit', ('name', 'price'))
f_prototype = FRUIT('', 0)  # 原型

def dict_to_FRUIT(x):
    return f_prototype._replace(**x)

a = {
    
    'name': 'meat', 'price': 30}
dict_to_FRUIT(a)
Fruit(name='meat', price=30)

里面有一个操作,假设a = {'name': 'meat', 'price': 30}是一个字典,则**a表示name='meat', price=30


2.7 字典的合并

最基本的方法,可以使用dict类型的update()方法来合并两个字典,如下:

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

print(merged['x'])

print(merged['y'])
2
print(merged['z'])
3

a['x'] = 13
merged['x']
1

可以观察到新建立的字典为merged,但是如果改变字典a中的x的值并不会影响字典mergeda的值。

另一种方法是调用collection.ChainMap类来构建,如下:

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

print(merged['z'])
3

a['x'] = 13
merged['x']
13

需要注意,Chainmap对两个字典进行合并,若其中存在相同的关键词,则按照顺序进行对关键词进行赋值,如merged['z']==a['z']
Chainmap对两个原始字典中的值发生变化,将会影响最终的merged中的值变化。


三、切片的小技巧

切片(slice)可以命名!如下:

record = 'apple....10....5'
NAME = slice(0, 5)
PRICE = slice(9,11)
NUM = slice(15,16)

print(record[NAME])
apple
print(record[PRICE])
10
print(record[NUM])
5

slice对象可以通过s.starts.stops.step查看属性,如下:

a = slice(5, 50, 2)
print(a.start)
5
print(a.stop)
50
print(a.step)
2

也可以调用indices(size)的方法自动地调整已生成deed切片,使其符合特定size,如下:

a = slice(1,20)
a.indices(10)
(1, 10, 1)

总结

查漏补缺~

参考:《Python cookbook 中文版》[美]David Beazley&Brian K. Jones 著

猜你喜欢

转载自blog.csdn.net/weixin_47691066/article/details/126995712