python cookbook——数据结构和算法

一、将序列分解为单独的变量

1.问题

将一个包含N个元素的序列,分解为N个单独的变量

2.解决方案

data = ['abc', 123, 91.1, [1,2,3]]
d1, d2, d3, d4 = data

d5, d6, d7, (year, month, day) = data

s = 'hello'
a, b, c, d, e = s

任何序列或者可迭代对象都可以通过赋值操作分解

当做分解操作时,若想去掉一些不用的值,可以用一个用不到的变量名,作为丢掉的值

data = ['abc', 123, 91.1, [1,2,3]]
abandon1, abandon2, abandon3, [year, month, day] = data

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

1.问题

一个可迭代对象元素很多,但要从其中分解出部分的值,全部罗列出来极其不方便

2.解决方法

例如需要对24个人的成绩统计平均分,但要去掉最高和最低分。

用 ”*表达式“

def drop_first_last(grades):
    first, *middle, last = grades
    return np.mean(middle)

其中middle是存在数组里的

import numpy as np

def drop_first_last(grades):
    first, *middle, last = grades
    print(middle)
    return np.mean(middle)


a = list(range(0,10))
print(drop_first_last(a))
#middle = [1,2,3,4,5,6,7,8]

把*看成是取可变长数组的符号更方便理解

实例:

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)  #*arg是x,y; arg是[x,y]
    if tag == 'bar':
        do_bar(*args)

一定要注意:*arg是x,y; arg是[x,y]

同理也可以用*abandon去舍弃那些不要的值

*号对于去掉中间元素或者截取中间元素很方便。

三、yield关键字

1.问题

实现一个自定义的可迭代类型,区别与range()等内置函数

2.解决方法

生成器通常跟着一个循环

def frange(start, stop, step):
    x = start
    while x < stop:
        yield x
        x += step

for n in frange(0, 3, 0.5):
    print(n)

另外:生成器只会相应迭代器操作,否则只返回一个可迭代对象。

def frange(start, stop, step):
    x = start
    while x < stop:
        yield x
        x += step

iteration = frange(0, 3, 1)
print(iteration)
print(next(iteration))
print(next(iteration))
print(next(iteration))
print(next(iteration))

1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,

4.程序执行print("*"*20),输出20个*

5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,

6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

生成器的理解:

一个带yield关键字的函数不再是一个函数,而变成了一个生成器对象,运行这个函数并不会执行,只有出现对可迭代对象操作的函数时,才运行。

yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始

而for循环会一直next()到底。

使用生成器的目的是为了节省内存。

3.遇到的问题

from collections import deque


def search(figures, pattern, history):
    label_record = deque(maxlen=2)
    for figure in list(enumerate(figures)):
        if pattern == figure[1]:
            label = figure[0]
            label_record .append(label)
            yield label, label_record 

            
a = [1, 2, 1, 3, 4, 5, 6, 3]
b = search(a , 3, history=2)
print(next(b))
print(next(b))
print(next(b))

print(next(search(a, 3, history=2)))
print(next(search(a, 3, history=2)))
print(next(search(a, 3, history=2)))

print(next(search(a, 3, history=2)))是一直在创建新的生成器!!!

要用上面的方法

四、保存最后N个元素

1.问题

希望在过程中对最后几项历史做一个统计。

2.解决方法

connections.deque(maxlen):双向队列 maxlen默认是无限长

例如:对文本进行文本匹配, 当发现有匹配时就输出当前的匹配行以及最后检查过的N行文本。

from collections import 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)
from collections import deque

q = deque(maxlen) #也可以不写maxlen表示无限长队列
q.append()
q.appendleft()
q.pop()
q.popleft()

队列在两端添加或者删除元素的复杂度都是O(1), 而列表的头部插入或移除元素复杂度是O(n)

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

1.问题

找到集合中最大或最小的N个元素

2.解决方法

heapq堆队列模块中 nlargest()和nsmallest()

import heapq

nums = [1, 4, 5, 6, 1, 9, 9, 12, -1]
print(heapq.nlargest(2, nums))
print(heapq.nsmallest(2, nums))

key关键字对字典的操作

这里的key把pro里的所有元素传给了lambda表达式,指定排序的是’fee'这个key

import heapq
pro = [ 
       {'fee': 1, 'ticket': 99},
       {'fee': 2, 'ticket': 89},
       {'fee': 3, 'ticket': 9},
       ]

print(heapq.nlargest(1, pro, key = lambda x: x['fee']))

3.找最大最小值问题的总结

1.当找最小和最大函数时

用max(), min()最快。

2.当找N大或N小,且N相对于总体数据来说很小,用heapq.nlargest() heapq.nsmallest()快。

3.当要N大或N小,且N和总体数据数量接近时,通过排序再切片更快。

六、贪心算法

1、基本概念:

     所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。

     贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

    所以对所采用的贪心策略一定要仔细分析其是否满足无后效性

2、贪心算法的基本思路:

    1.建立数学模型来描述问题。

    2.把求解的问题分成若干个子问题。

    3.对每一子问题求解,得到子问题的局部最优解。

    4.把子问题的解局部最优解合成原来解问题的一个解。

3、贪心算法适用的问题

      贪心策略适用的前提是:局部最优策略能导致产生全局最优解。

    实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

4、贪心算法的实现框架

    从问题的某一初始解出发;

    while (能朝给定总目标前进一步)

    { 

          利用可行的决策,求出可行解的一个解元素;

    }

    由所有解元素组合成问题的一个可行解;

  

5、贪心策略的选择

     因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。

6、例题分析

    下面是一个可以试用贪心算法解的题目,贪心解的确不错,可惜不是最优解。

    [背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。

    要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

    物品 A B C D E F G

    重量 35 30 60 50 40 10 25

    价值 10 40 30 50 35 40 30

    分析:

    目标函数: ∑pi最大

    约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)

    (1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?

    (2)每次挑选所占重量最小的物品装入是否能得到最优解?

    (3)每次选取单位重量价值最大的物品,成为解本题的策略。

    值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。

    贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。

    可惜的是,它需要证明后才能真正运用到题目的算法中。

    一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。

    对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下:

    (1)贪心策略:选取价值最大者。反例:

    W=30

    物品:A B C

    重量:28 12 12

    价值:30 20 20

    根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。

    (2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。

    (3)贪心策略:选取单位重量价值最大的物品。反例:

    W=30

    物品:A B C

    重量:28 20 10

    价值:28 20 10

    根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。

七、实现优先队列

1.问题

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

2.解决方法

import heapq

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]

1.关于python变量前后下划线的问题:

    前后没有下划线的是公有方法,

    前边有一个下划线的为私有方法或属性,子类无法继承,

    前边有两个下划线的 一般是为了避免于子类属性或者方法名冲突,无法在外部直接访问。

    前后都有双下划线的为系统方法或属性。

    后边单个下划线的可以避免与系统关键词冲突

2.对数据结构“堆”进行一个总结:

堆是一种特殊的树形数据结构,每个节点都有一个值,通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小),而且根节点的两个孩子也能与孩子节点组成子树,亦然称之为堆。
堆分为两种,大根堆和小根堆是一颗每一个节点的键值都不小于(大于)其孩子节点的键值的树。无论是大根堆还是小根堆(前提是二叉堆)都可以看成是一颗完全二叉树。下面以图的形式直观感受一下:
这里写图片描述

heap = [] #创建了一个空堆
heappush(heap,item) #往堆中插入一条新的值
item = heappop(heap) #从堆中弹出最小值。 

3.元组进行大小比较是逐项进行比较

代码详解:

import heapq

class PriorityQueue:
    def __init__(self):
        self._queue = [] #建立一个空堆
        self._index = 0 #建立一个索引来区分同级别优先级的pop顺序(依据进堆顺序)
        
    def push(self, item, priority):
        heapq.heappush(self._queue, (-priority, self._index, item))#priority前加负号的原因是,堆是先弹出最小值。
        self._index += 1
        
    def pop(self):
        return heapq.heappop(self._queue)[-1] #这里的[-1]是因为存入的item是一个元组(-priority, self._index, item), 我们要取的是item项。

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

1.问题

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

2.解决方法

方法一:采用数组或者列表作为value

#列表
d = {
     'money': [0, 1, 2],
     'tutu' : [3, 4, 5]
     }
#集合
e = {
     'money': {0, 1, 2},
     'tutu' : {3, 4, 5}
     }

要使用列表还是集合完全取决于应用的意图。如果希望保留元素插入的顺序,就用列表。如果希望消除重复元素(且不在意它们的顺序),就用集合。

方法二:collections模块中的defaultdict类

from collections import defaultdict

d = defaultdict(list)
d['mimi'].append(1)
d['mimi'].append(2)
d['mimi'].append(1)
d['tutu'].append(4)
print(d)

#注意集合用的是.add()
d = defaultdict(set)
d['mimi'].add(1)
d['mimi'].add(2)
d['mimi'].add(1)
d['tutu'].add(4)
print(d)

 要记住哈,往集合里面加东西,用的是.add()

关于defaultdict,需要注意的一个地方是,它会自动创建字典表项以待稍后的访问(即使这些表项当前在字典中还没有找到)例如,列表创建的defaultdict并没有‘lili’这一个key。

如果不想要上面那个功能可以对普通字典用方法:

d = {'b':[]}

d.setdefault('a', []).append(1)
d.setdefault('b', []).append(1)

注意,这里不可以将[]换做{}来用集合,只能用列表。但是很少有程序员这样用。

采用defaultdict的好处是可以让代码变得很简洁,例如比较下面代码:

from collections import defaultdict

d = {}
pairs = ['mimi', 'lili', 'titi']

for key, value in enumerate(pairs):
    if key not in d:
        d[key] = [value]


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

九、让字典保持有序

1.问题

我们想创建一个字典,同时当对字典做迭代或序列化操作时,也能控制其中元素的顺序。

2.解决方法

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

from collections import OrderedDict

d = OrderedDict()
d['mimi'] = 1
d['titi'] = 2
d['cici'] = 3
for key, value in d.items():
    print(key, value)

 OrderedDict内部维护了一个双向链表,OrderedDict的大小是普通字典的2倍多,这是由于它额外创建的链表所致。因此,如果打算构建一个涉及大量OrderedDict实例的数据结构(例如从CSV文件中读取100000行内容到OrderedDict列表中),那么需要认真对应用做需求分析,从而判断使用OrderedDict所带来的好处是否能超越因额外的内存开销所带来的缺点。

十、与字典有关的计算问题

1.问题

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

2.解决方法

zip函数的原型为:zip([iterable, …])

参数iterable为可迭代的对象,并且可以有多个参数。该函数返回一个以元组为元素的列表,其中第 i 个元组包含每个参数序列的第 i 个元素。返回的列表长度被截断为最短的参数序列的长度。只有一个序列参数时,它返回一个1元组的列表。没有参数时,它返回一个空的列表。

import numpy as np
a=[1,2,3,4,5]
b=(1,2,3,4,5)
c=np.arange(5)
d="zhang"
zz=zip(a,b,c,d)
print(zz)

输出:
[(1, 1, 0, 'z'), (2, 2, 1, 'h'), (3, 3, 2, 'a'), (4, 4, 3, 'n'), (5, 5, 4, 'g')]

当没有参数的时候

import numpy as np
zz=zip()
print(zz)

输出:[]

当只有一个参数的时候

import numpy as np
a=[1,2,3]
zz=zip(a)
print(zz)

输出:[(1,), (2,), (3,)]

当多个参数长度不同的时候

import numpy as np
a=[1,2,3]
b=[1,2,3,4]
c=[1,2,3,4,5]
zz=zip(a,b,c)
print(zz)

输出:[(1, 1, 1), (2, 2, 2), (3, 3, 3)]

下面我们来对股票数据进行操作:(记得之前提过元组可以比大小)

d = {'A':1,
     'B':2,
     'C':3,
     'D':4,
     'E':5}

min_price = min(zip(d.values(), d.keys()))
max_price = max(zip(d.values(), d.keys()))
price_sorted = sorted(zip(d.values(), d.keys()))

 这里要注意,zip()创建了一个迭代器,且只能被用一次。

d = {'A':1,
     'B':2,
     'C':3,
     'D':4,
     'E':5}
zz = zip(d.values(), d.keys())
min_price = min(zz)
print(min_price)
max_price = max(zz)
print(max_price)

3.讨论

事实上这种方法明智许多,来看看其它实现方法:

如果直接求min(d)会将健值直接比较,不可取,采用下面的方法:

d = {'A':1,
     'B':2,
     'C':3,
     'D':4,
     'E':5}

print(min(d, key=lambda x:d[x])) #返回键
print(d[min(d, key=lambda x:d[x])]) #返回值

十一、在两个字典中寻找相同点

1.问题

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

2.解决方法

常用集合操作汇总:

S | T 返回一个新的集合,包括在集合ST的所有元素

S - T 返回一个新集合,包括在集合S但不在T中的元素

S & T 返回一个新集合,包括同时在集合ST中的元素

S ^ T 返回一个新集合, 包括集合ST中不相同元素

字典就是一系列键和值之间的映射集合。字典的keys()方法会返回keys-view对象,其中暴露了所有的键。关于字典的键有一个很少有人知道的特性,那就是它们也支持常见的集合操作,比如求并集、交集和差集。因此,如果需要对字典的键做常见的集合操作,那么就能直接使用keys-view对象而不必先将它们转化为集合。字典的items()方法返回由(key,value)对组成的items-view对象。这个对象支持类似的集合操作,可用来完成找出两个字典间有哪些键值对有相同之处的操作。尽管类似,但字典的values()方法并不支持集合操作。部分原因是因为在字典中键和值是不同的,从值的角度来看并不能保证所有的值都是唯一的。单这一条原因就使得某些特定的集合操作是有问题的。但是,如果必须执行这样的操作,还是可以先将值转化为集合来实现。

a = {'x':1,
     'y':2,
     'z':3}

b = {'w':10,
     'x':11,
     'y':2}

print(a.keys() & b.keys())
print(a.keys() - b.keys())
print(a.items() & b.items())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值