Python数据结构和算法

Python数据结构和算法

1.序列分解为单独的变量

只要对象是可迭代的(字符串,文件,迭代器,生成器),那么就可以执行分解操作。



p = (4,5)
x,y = p
print(x)#4 
print(y)#5

data = ['hongsong', 40, 50.1, (2012, 12, 31)]
name, shares, price, date = data
print(name,shares,price,date)#hongsong 40 50.1 (2012, 12, 31)
name, shares, price, (year, month, day) = data
print(name, shares, price, (year, month, day))#hongsong 40 50.1 (2012, 12, 31)


s = 'Hello'
a,b,c,d,e = s
print(a,b,c,d,e)#H e l l o


data = ['hongsong', 40, 50.1, (2012, 12, 31)]
_, shares, _, data = data
print(shares)#40
print(data)#(2012, 12, 31)

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

#-----------*式的语法在迭代一个变长的元组序列时尤为有用------------------#

*trailing, current = [10, 8, 7, 1, 9, 5, 10, 3]
print(trailing)#[10, 8, 7, 1, 9, 5, 10]
print(current)#3


#----------星号表达式在迭代元素为可变长元组的序列时是很有用的。---------#
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)
#输出:
foo 1 2
bar hello
foo 3 4
#----------星号表达式在迭代元素为可变长元组的序列时是很有用的。---------#

#----------星号解压语法在字符串操作的时候也会很有用的。比如字符串的分割。---------#
line = 'nobody:*:-2:-2:Unprivileged User:/var/empty/:usr/bin/false'
uname, *fields, homedir, sh = line.split(':')
print(uname)#nobody
print(homedir)#/var/empty/
print(sh)#usr/bin/false
#----------星号解压语法在字符串操作的时候也会很有用的。比如字符串的分割。---------#

record = ('ACME', 50, 123.45, (12, 18 ,2012))
name, *_, (*_, year) = record
print(name)#ACME
print(year)#2012

3.保存最后N个元素

deque(maxlen=N)创建了一个固定长度的队列。
from collections import deque
'''对一系列文本行做简单的文本匹配操作,当发现有匹配时就输出当前的匹配行以及最后检查过的N行文本'''
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)

if __name__ == '__main__':
    with open('E:\hs.txt','r',encoding='utf-8') as f:
        for line,prevlines in search(f,'哈哈',5):
            for pline in prevlines:
                print(pline,end='')
            print(line, end='')
            print('_' * 20)
#-------当有新记录加入而队列已满时会自动移除最老的那条记录-------#
q = deque(maxlen=3)
q.append(1)
q.append(2)
q.append(3)
print(q)#deque([1, 2, 3], maxlen=3)
q.append(4)
q.append(5)
print(q)#deque([3, 4, 5], maxlen=3)
#-------当有新记录加入而队列已满时会自动移除最老的那条记录-------#
q = deque()
q.append(1)
q.append(2)
q.append(3)
print(q)#deque([1, 2, 3])
q.appendleft(4)
print(q)#deque([4, 1, 2, 3])
print(q.pop())#3
print(q)#deque([4, 1, 2])

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

heapd模块中有两个函数-----nlargest()和nsmallest()
import heapq

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

portfolio = [
    {'name':'IBM','shares':100,'price':91.9},
    {'name':'AAPL','shares':50,'price':543.22},
    {'name':'FB','shares':35,'price':16.35},
    {'name': 'FA', 'shares': 35, 'price': 14.35},
    {'name': 'FC', 'shares': 35, 'price': 155.35},
    {'name': 'FD', 'shares': 35, 'price': 13.35}
]
cheap = heapq.nsmallest(3,portfolio,key=lambda m:m['price'])
expensive = heapq.nlargest(3,portfolio,key=lambda m:m['price'])
print(cheap)
#[{'name': 'FD', 'shares': 35, 'price': 13.35}, {'name': 'FA', 'shares': 35, 'price': 14.35}, {'name': 'FB', 'shares': 35, 'price': 16.35}]
print(expensive)
#[{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FC', 'shares': 35, 'price': 155.35}, {'name': 'IBM', 'shares': 100, 'price': 91.9}]

通过优先队列可以构造堆,堆是一种实用的数据结构。
heapify()函数将列表生成堆,堆最重要的特性就是heap[0]总是最小的那个元素。
heappop(heap):弹出对中最小元素

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

注意:当要查找的元素个数相对比较小的时候,函数 nlargest() 和 nsmallest() 是很合适的。如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用 min() 和 max() 函数会更快些。类似的,如果 N 的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点(sorted(items)[:N] 或者是 sorted(items)[-N:] ).需要在正确场合使用函数nlargest()和 nsmallest() 才能发挥它们的优势(如果 N 快接近集合大小了,那么使用排序操作会更好些)。

5.实现优先级队列

import heapq

class PriorityQueue:

    def __init__(self):
        self._queue = []
        self._index = 0
    #heapq.heappush(heap,item):将item,推入heap
    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]

class Item:
    def __init__(self,name):
        self.name = name
    def __repr__(self):
        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())#Item('bar')
print(q.pop())#Item('spam')
print(q.pop())#Item('foo')
print(q.pop())#Item('grok')

注意:,第一个 pop() 操作返回优先级最高的元素。另外注意到如果两个有着相同优先级的元素(foo 和 grok ),pop 操作按照它们被插入到队列的顺序返回的。
函数 heapq.heappush() 和 heapq.heappop() 分别在队列 _queue 上插入和删除第一个元素,并且队列 _queue 保证第一 个元素拥有最高优先级。
heappop() 函数总是返回”最小 的”的元素,这就是保证队列 pop 操作返回正确元素的关键。另外,由于 push 和 pop 操作时间复杂度为 O(log N),其中 N 是堆的大小,因此就算是 N 很大的时候它们运行 速度也依旧很快。
在上面代码中,队列包含了一个 (-priority, index, item) 的元组。优先级为负数的目的是使得元素按照优先级从高到低排序。这个跟普通的按优先级从低到高排序 的堆排序恰巧相反。
index 变量的作用是保证同等优先级元素的正确排序。通过保存一个不断增加的index下标变量,可以确保元素按照它们插入的顺序排序。而且,index 变量也在相同 优先级元素比较的时候起到重要作用。

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

一个字典就是一个键对应一个单值的映射。如果你想要一个键映射多个值,那么你 就需要将这多个值放到另外的容器中,比如列表或者集合里面。
如果你想保持元素的插入顺序就应该使用列表,如果想去掉重复元素就使用集合(并且不关心元素的顺序问题)。
用collections 模块中的 defaultdict 来构造字典。defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要关注添加元素操作了。需要注意的是,defaultdict 会自动为将要访问的键(就算目前字典中并不存在这样的键)创建映射实体。如果你并不需要这样的特性,你可以在一个普通的字典上使用 setdefault() 方法来代替。但每次调用setdefault() 方法都得创建一个新的初始值的实例(例子程序中的空列表 [] )。


from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)

print(d)#defaultdict(<class 'list'>, {'a': [1, 2], 'b': [4]})

d = {}
d.setdefault('a',[]).append(1)
d.setdefault('a',[]).append(2)
d.setdefault('b',[]).append(4)
print(d)#{'a': [1, 2], 'b': [4]}

7.让字典保持有序

为了能控制一个字典中元素的顺序,你可以使用 collections 模块中的OrderedDict 类。在迭代操作的时候它会保持元素被插入时的顺序,示例如下:

from collections import OrderedDict

d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
print(d)
for key in d:
    print(key,d[key])
#当你想要构建一个将来需要序列化或编码成其他格式的映射的时候,OrderedDict是非常有用的。
import json
print(json.dumps(d))

输出:

OrderedDict([('foo', 1), ('bar', 2), ('spam', 3), ('grok', 4)])
foo 1
bar 2
spam 3
grok 4
{"foo": 1, "bar": 2, "spam": 3, "grok": 4}

OrderedDict 内部维护着一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来的时候,它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。需要注意的是,一个 OrderedDict 的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。所以如果你要构建一个需要大量 OrderedDict 实例的数据结构的时候(比如读取 100,000 行 CSV 数据到一个 OrderedDict 列表中去),那么你就得仔细权衡一下是否使用 OrderedDict 带来的好处要大过额外内存消耗的影响。

8.与字典有关的计算问题

为了对字典值执行计算操作,通常需要使用 zip() 函数先将键和值反转过来

prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}
#-----求最小-----#
min_price = min(zip(prices.values(),prices.keys()))
print(min_price)
#-----求最大-----#
max_price = max(zip(prices.values(),prices.keys()))
print(max_price)
#-----排序------可以使用 zip() 和 sorted() 函数来排列字典数据#
sorted_price = sorted(zip(prices.values(),prices.keys()))
print(sorted_price)

print(min(prices, key=lambda k: prices[k]))#FB
min_values = prices[min(prices, key=lambda k: prices[k])]
print(min_values)#10.75

输出:

(10.75, 'FB')
(612.78, 'AAPL')
[(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')]
FB
10.75

**需要注意的是 zip() 函数创建的是一个只能访问一次的迭代器。**比如,下面的代码就会产生错误:

prices_and_names = zip(prices.values(), prices.keys()) 
print(min(prices_and_names)) # OK 
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence

9.在两个字典中寻找相同点

a = {
    'x': 1,
    'y': 2,
    'z': 3
}
b = {
    'w': 10,
    'x': 11,
    'y': 2
}
#找到相同的键
print(a.keys() & b.keys())
#找到在a中而不在b中的键
print(a.keys() - b.keys())
#找到相同的键值对
print(a.items() & b.items())
#这些操作也可以用于修改或者过滤字典元素。比如,假如你想以现有字典构造一个 排除几个指定键的新字典。
#下面利用字典推导来实现这样的需求:
c = {key:a[key] for key in a.keys() - {'z','w'}}
print(c)

输出:

{'x', 'y'}
{'z'}
{('y', 2)}
{'x': 1, 'y': 2}

一个字典就是一个键集合与值集合的映射关系。字典的keys() 方法返回一个展现键集合的键视图对象。键视图的一个很少被了解的特性就是它们也支持集合操作,比如集合并、交、差运算。所以,如果你想对集合的键执行一些普通的集合操作,可以直接 使用键视图对象而不用先将它们转换成一个 set。
字典的 items() 方法返回一个包含 (键,值) 对的元素视图对象。这个对象同样也 支持集合操作,并且可以被用来查找两个字典有哪些相同的键值对。
尽管字典的 values() 方法也是类似,但是它并不支持这里介绍的集合操作。某种 程度上是因为值视图不能保证所有的值互不相同,这样会导致某些集合操作会出现问题。不过,如果你硬要在值上面执行这些集合操作的话,你可以先将值集合转换成 set,然后再执行集合运算就行了。

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

#-------如果序列中的值是可希哈的(整数,浮点数,字符串,元组)---------#
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)))

#-------如果序列中的值是不可希哈的(列表)---------#
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)
a = [{'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
#这里的 key 参数指定了一个函数,将序列元素转换成 hashable 类型
print(list(dedupe(a, key=lambda d: (d['x'],d['y']))))
print(list(dedupe(a, key=lambda d: d['x'])))

输出:

[1, 5, 2, 9, 10]
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]

如果如果你想读取一个文件,消除重复行,你可以很容易像这样做:

with open(somefile,'r') as f:
	 for line in dedupe(f): ...

11.对切片命名

内置的 slice() 函数创建了一个切片对象,可以被用在任何允许切片使用的地方。

record = '1kfjajfjoj33333333333330o03k00908997777979786669799987'
SHARES = slice(14,17)
PRICE = slice(30,33)
cost = int(record[SHARES]) * int(record[PRICE])
print(cost)


items = [0,1,2,3,4,5,6]
a = slice(2,4)
print(items[a])
items[a] = [10,11]
print(items)
del items[a]
print(items)

print(a.start)
print(a.stop)
print(a.step)

#另外,你还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序列上,这个方法返回一个三元组 (start, stop, step) ,
#所有值都会被合适的缩小满足边界限制,从而使用的时候避免出现 IndexError 异常。
s = 'HelloWord'
print(a.indices(len(s)))
for i in range(*a.indices(len(s))):
    print(s[i])

输出:

302364
[2, 3]
[0, 1, 10, 11, 4, 5, 6]
[0, 1, 4, 5, 6]
2
4
None
(2, 4, 1)
l
l

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

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)
 # Outputs [('eyes', 8), ('the', 5), ('look', 4)]

Counter 对象可以接受任意的由**可哈希(hashable)**元素构成的序列对象。在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。
Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合。

a = Counter(words) 
b = Counter(morewords)
print(a)
#Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, "you're": 1, "don't": 1, 
#'under': 1, 'not': 1}) 
print(b)
#Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
# 'my': 1, 'why': 1}) 
# Combine counts 
c = a + b 
print(c)
#Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, 
#"you're": 1, "don't": 1, 'in': 1, 'why': 1, 'looking': 1, 'are': 1, 'under': 1, 'you': 1}) 
# Subtract counts
d = a - b 
print(d)
#Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2, "you're": 1, "don't": 1, 'under': 1})

毫无疑问,Counter 对象在几乎所有需要制表或者计数数据的场合是非常有用的工具。

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

使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构。

from operator import itemgetter

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'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))

print(rows_by_fname)
print(rows_by_uid)

rows_by_uid_fname = sorted(rows, key=itemgetter('uid', 'fname'))
print(rows_by_uid_fname)
#itemgetter() 有时候也可以用 lambda 表达式代替,比如:
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
rows_by_lfname = sorted(rows, key=lambda r: (r['lname'], r['fname']))
print(rows_by_fname)
print(rows_by_lfname)

#不要忘了这节中展示的技术也同样适用于 min() 和 max() 等函数。比如:
rows_by_min = min(rows, key=itemgetter('uid'))
print(rows_by_min)
rows_by_max = max(rows, key=itemgetter('uid'))
print(rows_by_max)

输出:

[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}

在上面例子中,rows 被传递给接受一个关键字参数key的sorted() 内置函数。这个参数是 callable 类型,并且从 rows 中接受一个单一元素,然后返回被用来排序的值。itemgetter() 函数就是负责创建这个callable 对象的。
operator.itemgetter() 函数接受的参数可作为查询的标记,用来从rows的记录中提取出所需要的值。该参数可以是一个字典的键名称,一个整型值或者任何能够传入一个对象的 getitem() 方法的值。
如果你传入多个标记给 itemgetter() ,它生成的 callable 对象会返回一个包含所有元素值的元组,并且 sorted() 函数会根据这个元组中元素顺序去排序。但你想要同时在几个字段上面进行排序(比如通过姓和名来排序,也就是例子中的那样) 的时候这种方法是很有用的。
但是,使用 itemgetter() 方式会运行的稍微快点。因此,如果 你对性能要求比较高的话就使用 itemgetter() 方式。

14.对不原生支持比较操作的对象排序

内置的 sorted() 函数有一个关键字参数 key ,可以传入一个 callable 对象给它,这个 callable 对象对每个传入的对象返回一个值,这个值会被 sorted 用来排序这些对象。比如,如果你在应用程序里面有一个 User 实例序列,并且你希望通过他们 的 user_id 属性进行排序,你可以提供一个以 User 实例作为输入并输出对应 user_id 值的 callable 对象。比如:

class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __repr__(self):
        return 'User({})'.format(self.user_id)

users = [User(23), User(3), User(99)]
print(users)

sort_id = sorted(users, key=lambda u: u.user_id)
print(sort_id)

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

sort_id = sorted(users, key=attrgetter('user_id'))
print(sort_id)

min_id = min(users, key=attrgetter('user_id'))
print(min_id)

max_id = max(users, key=attrgetter('user_id'))
print(max_id)

输出:

[User(23), User(3), User(99)]
[User(3), User(23), User(99)]
[User(3), User(23), User(99)]
User(3)
User(99)

选择使用 lambda 函数或者是 attrgetter() 可能取决于个人喜好。但是,**attrgetter() 函数通常会运行的快点,**并且还能同时允许多个字段进行比较。

15.根据字段将记录分组

itertools.groupby() 函数对于数据分组操作非常实用。

from operator import itemgetter
from itertools import 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'},
]
#现在假设你想在按 date 分组后的数据块上进行迭代。为了这样做,你首先需要按 照指定的字段 (这里就是 date ) 排序,
#然后调用itertools.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)
for row in rows:
    rows_by_date[row['date']].append(row)
for r in rows_by_date['07/01/2012']:
    print(r)

输出:

{'address': '5412 N CLARK', 'date': '07/01/2012'}
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}

16.筛选序列中的元素

最简单的筛选序列元素的方法就是使用列表推导.

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

输出:

[1, 4, 10, 2, 3]
[-5, -7, -1]

使用列表推导的一个潜在缺陷就是如果输入非常大的时候会产生一个非常大的结果集,占用大量内存。如果你对内存比较敏感,那么你可以使用生成器表达式迭代产生筛选出来的元素。比如:

pos = (n for n in mylist if n > 0)
for x in pos:
    print(x)

输出:

1
4
10
2
3

有时候,筛选规则比较复杂,不能简单的在列表推导或者生成器表达式中表达出来。比如,假设筛选的时候需要处理一些异常或者其他复杂情况。这时候你可以将筛选代码放到一个函数中,然后使用内建的 filter() 函数。示例如下:

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

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

ivals = list(filter(is_int, values))
print(ivals)

输出:

['1', '2', '-3', '4', '5']

filter() 函数创建了一个迭代器,因此如果你想得到一个列表的话,就得像示例那样使用 list() 去转换。

列表推导和生成器表达式通常情况下是筛选数据最简单的方式。其实它们还能在筛选的时候转换数据。比如:

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

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

输出:

[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
[1, 4, 0, 10, 0, 2, 3, 0]

另外一个值得关注的筛选工具就是 itertools.compress() ,它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数。然后输出 iterable 对象中对 应选择器为 True 的元素。当你需要用另外一个相关联的序列来过滤某个序列的时候, 这个函数是非常有用的。比如,假如现在你有下面两列数据:

from itertools import compress

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]

more5 = [n > 5 for n in counts]
print(more5)

print(list(compress(addresses, more5)))

输出:

[False, False, True, False, False, True, True, False]
['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']

这里的关键点在于先创建一个 Boolean 序列,指示哪些元素符合条件。然后 compress() 函数根据这个序列去选择输出对应位置为 True 的元素。
和 filter() 函数类似,compress() 也是返回的一个迭代器。因此,如果你需要得 到一个列表,那么你需要使用 list() 来将结果转换为列表类型。

17.从字典中提取子集

最简单的方式是使用字典推导。比如:

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}
print(p1)
#大多数情况下字典推导能做到的,通过创建一个元组序列然后把它传给 dict() 函 数也能实现。
#但是,字典推导方式表意更清晰,并且实际上也会运行的更快些。
p1 = dict((key, value) for key, value in prices.items() if value > 200)
print(p1)
#--------------------------------------------------------------------------------#
#但是,运行时间测试结果显示这种方案大概比第一种方案慢
tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p2 = {key: value for key, value in prices.items() if key in tech_names}
print(p2)
p2 = {key: prices[key] for key in prices.keys() & tech_names}
print(p2)

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

collections.namedtuple() 函数通过使用一个普通的元组对象来帮你解决这个问题。这个函数实际上是一个返回 Python中标准元组类型子类的一个工厂方法。你需要传递一个类型名和你需要的字段给它,然后它就会返回一个类,你可以初始化这个类, 为你定义的字段传递值等。、

from collections import namedtuple

Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
sub = Subscriber('19080166480@163.com', '2019-04-25')
print(sub)
print(sub.addr)
print(sub.joined)
print(len(sub))
#尽管 namedtuple 的实例看起来像一个普通的类实例,但是它跟元组类型是可交换 的,支持所有的普通元组操作,比如索引和解压。比如:
address, jion = sub
print(address)
print(jion)

输出:

Subscriber(addr='19080166480@163.com', joined='2019-04-25')
19080166480@163.com
2019-04-25
2
19080166480@163.com
2019-04-25

命名元组的一个主要用途是将你的代码从下标操作中解脱出来。因此,如果你从数据库调用中返回了一个很大的元组列表,通过下标去操作其中的元素,当你在表中添加了新的列的时候你的代码可能就会出错了。但是如果你使用了命名元组,那么就不会有这样的顾虑。

命名元组另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间。如 果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。但是 需要注意的是,不像字典那样,一个命名元组是不可更改的。比如:

Stock = namedtuple('Stock',['name', 'shares', 'price'])
s = Stock('NIKE', 100, 123.45)
print(s)
# s.shares = 75# 报错:AttributeError: can't set attribute 因为namedtuple是不可变的

#如果你真的需要改变属性的值,那么可以使用命名元组实例的 _replace() 方法, 
#它会创建一个全新的命名元组并将对应的字段用新的值取代。比如:
s = s._replace(shares=75)
print(s)

输出:

Stock(name='NIKE', shares=100, price=123.45)
Stock(name='NIKE', shares=75, price=123.45)

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

from collections import namedtuple 
Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
# Create a prototype instance 
stock_prototype = Stock('', 0, 0.0, None, None)
# Function to convert a dictionary to a Stock
 def dict_to_stock(s):
  return return stock_prototype._replace(**s)

a = {'name': 'ACME', 'shares': 100, 'price': 123.45} 
print(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'} 
print(dict_to_stock(b)) 
#Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)

19.同时对数据做转换和换算

当生成器表达式作为一个单独参数传递给函数时候你并不需要多加一个括号。

files = os.listdir('C:/Users/hongsongyangyang/PycharmProjects/PythonCookBook/第一章数据结构和算法')
if any(name.endswith('.py') for name in files):
    print('There be python!')
else:
    print('Sorry, no python')

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

一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。然后,这些字典并不是真的合并在一起了,ChainMap 类只是在内部创建了一个容纳这些字典的列表并重 新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的,比如:

from collections import ChainMap

a = {'x': 1, 'z': 3}
b = {'y': 2, 'z': 4}
c = ChainMap(a, b)
print(c['x'])
print(c['y'])
print(c['z'])


print(len(c))
print(list(c.keys()))
print(list(c.values()))
print(c)

输出:

1
2
3
3
['y', 'z', 'x']
[2, 3, 1]
ChainMap({'x': 1, 'z': 3}, {'y': 2, 'z': 4})

如果出现重复键,那么第一次出现的映射值会被返回。因此,例子程序中的 c[‘z’] 总是会返回字典 a 中对应的值,而不是 b 中对应的值。

对于字典的更新或删除操作总是影响的是列表中第一个字典。比如:

c['z'] = 10
c['w'] = 40
print(c)
# del c['y']#KeyError: "Key not found in the first mapping: 'y'" 修改映射的操作总是会作用在列出的第一个映射结构上
del c['x']
print(c)

输出:

ChainMap({'x': 1, 'z': 10, 'w': 40}, {'y': 2, 'z': 4})
ChainMap({'z': 10, 'w': 40}, {'y': 2, 'z': 4})

ChainMap 对于编程语言中的作用范围变量(比如 globals , locals 等)是非常有 用的。事实上,有一些方法可以使它变得简单:

values = ChainMap()
values['x'] = 1
values = values.new_child()
values['x'] = 2
values = values.new_child()
values['x'] = 3
print(values)

print(values['x'])
values = values.parents
print(values['x'])
values = values.parents
print(values['x'])

输出:

ChainMap({'x': 3}, {'x': 2}, {'x': 1})
3
2
1

作为 ChainMap 的替代,你可能会考虑使用 update() 方法将两个字典合并。比如:

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

a['x'] = 13
print(merged['x'])#任何一个原始字典做了修改,这个改变都不会反应到合并后的字典中
print(a['x'])

输出:

{'y': 2, 'z': 3, 'x': 1}
1 2 3
1
13
'''ChainMap使用的就是原始字典'''
a = {'x': 1, 'z': 3}
b = {'y': 2, 'z': 4}
merged = ChainMap(a, b)
print(merged['x'])
a['x'] = 99
print(merged['x'])

输出:

1
99
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值