Python Cookbook 2 ——搜索和排序

Donald Knuth教授曾在《 The Art of Computer Programming》中提到:有很多计算机花了超过一半的计算时间在排序上。可以理解为:

  • 确实有很多非常重要的和排序有关的应用;
  • 有很多人在进行一些不必要的排序;
  • 低效的排序算法被广泛应用造成了计算时间的浪费。

在Python中,对于排序的策略:

  • 当需要排序的时候,尽量设法使用内建Python列表的sort方法;
  • 当需要搜索的时候,尽量设法使用内建的字典

最常用的是decorate-sort-undecorate(DSU)模式,这是一种通用的方法:通过创建一个辅助的列表,将问题转化为列表的排序,从而可以使用默认的快速的sort方法。

对字典的键排序

  • 根据键将值排序:
def sortedDictValues1(adict):
    keys = adict.keys()
    keys.sort()
    return [adict[key] for key in keys]


----------


def sortedDictValues2(adict):
    keys = adict.keys()
    keys.sort()
    return map(adict.get,keys)

不区分大小写对字符串列表排序

  • 采用DSU方式:先创建一个辅助列表,每个元素都是元组,元组的元素则来自原列表,并当做“键”处理。
def case_insenseitive_sort1(string_list):
    alist = [(x.lower(),x) for x in string_list]
    alist.sort()
    return [x[1] for x in alist]#不改变原列表


----------


def case_insenseitive_sort2(string_list):
    alist = [(x.lower(),x) for x in string_list]
    alist.sort()
    string_list[:] = [x[1] for x in alist]#直接作用于原列表,改变原列表
  • 更快捷的方式:
def case_insensitive_sort(string_list):#适用于普通字符串和unicode对象
    return sorted(string_list,key=lambda s:s.lower())

根据对应值将键或索引排序

  • 柱状图,实际上就是基于各种不同元素出现的次数,根据对应值将键或索引排序。下面是dict的一个子类,它为了这种应用加入了两个方法:
class hist(dict):
    def add(self,item,increment=1):
        '''为item的条目增加计数'''
        self[item] = 1 + self.get(item,0)
    def counts(self,reverse=False):
        '''返回根据对应值排序的键的列表'''
        aux = [(self[k],k) for k in self]
        aux.sort()
        if reverse:
            aux.reverse()
        return [k for v,k in aux]


----------


#跑个小例子:
if __name__ == '__main__':
    sentence = 'hello world hello python'
    words = sentence.split()
    c = hist()
    for word in words:
        c.add(word)
    print 'ascending count:'
    print c.counts()
    print 'decending count:'
    print c.counts(reverse=True)
#输出:
ascending count:
[(1, 'python'), (1, 'world'), (2, 'hello')]
decending count:
[(2, 'hello'), (1, 'world'), (1, 'python')]
  • 如果想将元素的统计结果放到一个列表中,做法也非常类似:
class hist(list):
    def __init__(self,n):
        '''初始化列表,统计n个不同项的出现'''
        list.__init__(self,n*[0])
    def add(self,item,increment=1):
        '''为item的条目增加计数'''
        self[item] += increment
    def counts(self,reverse=False):
        '''返回根据对应值排序的键的列表'''
        aux = [(v,k) for k,v in enumerate(self)]
        aux.sort()
        if reverse:
            aux.reverse()
        return [k for v,k in aux]
  • 更简单快速的方法:
def sorted_keys(the_dict,keys,reverse):
    return sorted(keys,key=the_dict.__getitem__,reverse=reverse)

根据内嵌的数字将字符排序

  • 首先将字符串按照数字与非数字切割开,然后将序列中的str型数字转化为int型数字,最后将其放在元组作为排序的键。例如下面这个字符串列表进行排序:files = ['weibo9.txt','weibo6.txt','weibo2.txt','weibo8.txt']按照上述思路:
import re
def find_numbers(elem):
    re_digits = re.compile(r'(\d+)')
    pieces = re_digits.split(elem)
    pieces[1] = int(pieces[1])
    return pieces[1]


----------


def sort_by_find_numbers1(the_list):
    aux = [(find_numbers(i),i) for i in the_list]
    aux.sort()
    return [j for i,j in aux]

files = ['weibo9.txt','weibo6.txt','weibo2.txt','weibo8.txt']
print ' '.join(sort_by_find_numbers1(files))#更美观的输出
print sort_by_find_numbers1(files)#输出为列表

#输出:
weibo2.txt weibo6.txt weibo8.txt weibo9.txt

['weibo2.txt', 'weibo6.txt', 'weibo8.txt', 'weibo9.txt']
  • 更快捷的操作(使用上面定义的find_numbers函数):
def sort_by_find_numbers2(the_list):
    return sorted(the_list,key=find_numbers)

以随机顺序处理列表的元素

  • 最简单和最高效的方法:首先对序列洗牌,然后线性访问洗完牌的序列:
import random 
def process_all_in_random(data,process):
    data = list(data)
    random.shuffle(data)
    for elem in data:
        process(elem)
  • 假如要从列表中随机挑选出一个元素,进行相应的处理后,将该元素删除。
import random
def process_random_and_moveing(data,process):
    while data:
        elem = random.choice(data)
        a = process(elem)
        print a
        data.remove(elem)

if __name__ == '__main__':
    d = ['1','2','3','4','5','5','5']
    process_random_and_moving(d,int)#
#输出:
2
5
1
5
3
4
5
  • process_random_and_moving函数运行效率不高,因为每个data.remove都会线性地搜索整个列表以获取要删除的元素。

在添加元素时保持序列顺序

某序列需要在不断加入新元素后,扔处于排序完毕状态,以便检查或者删除当前序列中最小的元素。

  • 首先会想到,可以调用the_list.sort()排序,然后用the_list.pop(0)来获取和删除最小的元素。
  • 引入的组织数据的结构。这是一种简洁的二叉树,能确保父节点总是比子节点小。在Python中维护一个堆的最好方式是使用列表,这个列表无需完成排序,却能够保证得到当前最小的元素,然后所有节点会被调整,以确保堆特性仍然有效:
the_list = [4,1,7,5,2,9,0]
import heapq
heapq.heapify(the_list)
heapq.heappop(the_list)#返回并删除最小元素
heapq.heapreplace(the_list,10)#加入一个新元素后,返回并删除之前最小的元素

获取序列中最小的几个元素

  • 可以先将序列排序,再使用sep[:n]获取最小的n个元素。
  • 或者使用简单可行的生成器:
import heapq
def sort_(data):
    data = list(data)
    heapq.heapify(data)
    while data:
        yield heapq.heappop(data)
  • 还有更快捷的方法:
import heapq
def n_smallest(n,data):#n个最小
    return heapq.nsmallest(n,data)

----------

def n_largest(n,data):#n个最大
    return heapq.nlargest(n,data)

----------

#也可不通过函数
heapq.heapify(data)
heapq.nsmallest(n,data)
heapq.nlargest(n,data)
发布了93 篇原创文章 · 获赞 97 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/duxu24/article/details/54354662