python之数据解构和算法进阶

1、解压赋值多个变量

采用解构的方法。可迭代对象才可以,变量数量与元素个数要一一对应,或者采用*万能接收。

2、解压可迭代对象赋值多个变量

如果一个可迭代对象的元素个数超过变量个数时,会抛出一个 ValueError

去掉最大值和最小值的方法:

def drop_first_last(grades):
    first, *middle, last = grades
    return avg(middle)
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)

3、保留最后N个元素

deque 双向队列 
from collections import deque 
1、deque提供了类似list的操作方法:
d=deque()
d.append(3)
2、两端都可以pop
d.pop()默认删除右边
d.popleft()删除左边的
3、deque(maxlen=20) 限制双向队列的长度,当循环的值超过限制数的项时,另一边的项会自动删除
4、添加list各项到deque中
d.extend()默认迭代添加到尾部
d.extendleft()迭代添加到左边
【在队列两端插入或删除元素时间复杂度都是 O(1) ,区别于列表,在列表的开头插入或删除元素的时间复杂度为 O(N) 。】

4、查找最大或最小的N个元素

import heapq   
#该模块提供了堆排序算法的实现。堆是二叉树,最大堆中父节点大于或等于两个子节点,最小堆父节点小于或等于两个子节点。
#创建堆
1、空列表。使用heapq.heappush()函数把值加入堆中
2、使用heap.heapify(list)转换列表成为堆结构
【堆创建好后,可以通过`heapq.heappop() 函数弹出堆中最小值
如果需要删除堆中最小元素并加入一个元素,可以使用heapq.heaprepalce() 函数】
【获取堆最大或最小值
如果需要获取堆中最大或最小的范围值,则可以使用heapq.nlargest() 或heapq.nsmallest() 函数】
这两个函数还接受一个key参数,用于dict或其他数据结构类型使用
import heapq
from pprint import pprint
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'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
pprint(cheap)
pprint(expensive)
堆最重要的特性就是,heap[0]总是最小的那个元素,此外,接下来的最小元素可以一次通过heapq.heappop()的方法轻松的找到。该方法会将第一个元素(最小的)弹出,然后以第二小的元素取而代之。

5、实现一个优先级队列

6、字典中的键映射多个值

怎样实现一个键对应多个值的字典(也叫 multidict)?

使用 `collections` 模块中的 `defaultdict` 来构造这样的字典。 `defaultdict` 的一个特征是它会自动初始化每个 `key` 刚开始对应的值
from collections import 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)

字典中一个键对应多个值方法两种:
1、使用defaultdict()
2、setdefault()
d = {} # 一个普通的字典
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
d.setdefault('b', []).append(4)

7、字典排序

8、字典运算

字典执行计算操作(最大值/最小值/排序)

prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}
采用zip()
print(max(zip(prices.values(),prices.keys()))) 最大值
zip可以把字典的键和值相互跌倒
print(min(zip(prices.values(),prices.keys())))  最小值

字典的值排序:
print(sorted(zip(prices.values(),prices.keys())))
【注意的是 zip() 函数创建的是一个只能访问一次的迭代器。】

【 min() 和 max() 函数中提供 key 函数参数来获取最小值或最大值对应的键的信息】
min(prices, key=lambda k: prices[k]) # Returns 'FB'
max(prices, key=lambda k: prices[k]) # Returns 'AAPL'


9、查找两字典的相同点

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

b = {
    'w' : 10,
    'x' : 11,
    'y' : 2
}
print(a.keys() & b.keys())   查找字典的键相同的
print(a.items() & b.items())  查找字典的值相同的
【假如你想以现有字典构造一个排除几个指定键的新字典。 下面利用字典推导来实现这样的需求:】
c = {key:a[key] for key in a.keys() - {'z', 'w'}}

c = { key for key in a.keys()  - {"x"}}
print(c)  采用集合的差集,然后转成字典。 #{'z': 3, 'y': 2}

c = { key :a[key ]for key in a.keys()  - {"x"}}
print(c)  #{'z': 3, 'y': 2}

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]
>>> list(dedupe(a))
[1, 5, 2, 9, 10]
>>>
消除元素不可哈希(比如 dict 类型)的序列中重复元素的话
def dedupe(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        print(val)
        if val not in seen:
            # print(item)
            yield item   #因为下面强转成list,所以,生成器中的元素会一一打印出来,其实是python内部帮我们实现了for循环
            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"]))))

11、命名切片

假定你要从一个记录(比如文件或其他类似格式)中的某些固定位置提取字段
######    0123456789012345678901234567890123456789012345678901234567890'
record = '....................100 .......513.25 ..........'
cost = int(record[20:23]) * float(record[31:37])
改进版:
【slice() 函数实现切片对象,主要用在切片操作函数里的参数传递。
"""

class slice(stop)
class slice(start, stop[, step])
参数说明:
start -- 起始位置
stop -- 结束位置
step -- 间距
"""
】
SHARES = slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])


"""
还可以通过调用切片的 indices(size) 方法将它映射到一个已知大小的序列上。 这个方法返回一个三元组 (start, stop, step) ,所有的值都会被缩小,直到适合这个已知序列的边界为止。 这样,使用的时就不会出现 IndexError 异常

"""
s = "helloword"
a = slice(2,10,2)
print(a.indices(len(s)))
for i in range(*a.indices(len(s))):
    print(s[i])
    

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

你有一个单词列表并且想找出哪个单词出现频率最高
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
wored_counts = Counter(words)
#得到的是Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
将单词出现频率统计出来并且,转换成Counter的类,高仿字典,可以使用dict转成字典。
print(dict(wored_counts))
top_w = wored_counts.most_common(3) 统计出现次数最多的三个三次
print(dict(top_w))
#{'eyes': 8, 'the': 5, 'look': 4}

Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合
morewords = ['why','are','you','not','looking','in','my','eyes']
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'
]
>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})
>>> # Combine counts
>>> c = a + b
>>> 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
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})
>>>

13、通过某个关键字排序一个字典的列表

你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表
#通过使用 operator 模块的 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}
]
根据任意的字典字段来排序输入结果行是很容易实现的,代码示例:

from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)
代码的输出如下:

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

itemgetter() 函数也支持多个 keys
rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
print(rows_by_lfname)

itemgetter()可以使用lambda代替
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
rows_by_lfname = sorted(rows, key=lambda r: (r['lname'],r['fname']))
优先使用itemgetter()比lambda效率快
还可以适用:
>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
练习:
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}
]
from  operator import  itemgetter
print(sorted(rows,key = itemgetter("fname","uid")))


14、通过某个字段将记录分组

itertools.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'},
]
from  operator import  itemgetter
from  itertools import  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 函数返回值相同)的元素序列。 在每次迭代的时候,它会返回一个值和一个迭代器对象, 这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。

15、过滤序列元素

数据序列,想利用一些规则从中提取出需要的值或者是缩短序列

方法一:
列表推导式
缺点占用内存
方法二:
生成器
方法三:
内置函数 filter()

【过滤操作的一个变种就是将不符合条件的值用新的值代替,而不是丢弃它们。】
采用:
clip_neg = [n if n > 0 else 0 for n in mylist]

过滤工具:
itertools.compress()
它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数。 然后输出 iterable 对象中对应选择器为 True 的元素。 当你需要用另外一个相关联的序列来过滤某个序列的时候,这个函数是非常有用的
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]
>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']
这里的关键点在于先创建一个 Boolean 序列,指示哪些元素符合条件。 然后 compress() 函数根据这个序列去选择输出对应位置为 True 的元素。

16、字典中提取子集

构造一个字典,它是另外一个字典的子集
方法一:
字典推导式
prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}
# Make a dictionary of all prices over 200
p1 = {key: value for key, value in prices.items() if value > 200}
# Make a dictionary of tech stocks
tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p2 = {key: value for key, value in prices.items() if key in tech_names}
方法二:
元祖传递给dict()函数
p1 = dict((key, value) for key, value in prices.items() if value > 200)
方法三:
# Make a dictionary of tech stocks
tech_names = { 'AAPL', 'IBM', 'HPQ', 'MSFT' }
p2 = { key:prices[key] for key in prices.keys() & tech_names }

性能比较,方法一>方法三>方法二

17、映射名称到序列元素

#问题“
"""
你有一段通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的代码难以阅读, 于是你想通过名称来访问元素。
"""
这个函数实际上是一个返回 Python 中标准元组类型子类的一个工厂方法。 你需要传递一个类型名和你需要的字段给它,然后它就会返回一个类,你可以初始化这个类,为你定义的字段传递值等
代码实例:
  sub = namedtuple("sub",['addr', 'joined'])
subb = sub('jonesy@example.com', '2012-10-19')
print(list(subb))



命名元组(利用 collections 模块中的 namedtuple 函数)
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['name', 'age', 'sex', 'email'])
>>> s = Student('Jim', 21, 'male', '123@qq.com')
>>> s.name
'Jim'
>>> s.age
21

namedtuple 函数这里接收两个参数,第一个参数为要创建类型的名称,第二个参数是一个列表,代表了每一个索引的名字。当建立完这个 Student 类之后,就可以使用正常的构造方法来构造新的对象如 s,并且可以直接通过访问属性的方式来访问所需要的值。
此时使用isinstance函数对比内置的tuple:

>>> isinstance(s, tuple)
True
可见用namedtuple构造出来的类其本质就是一个tuple元组,所以仍然可以使用下标的方式来访问属性。并且在任何要求类型为元组的地方都可以使用这个namedtuple。

命名元祖中的值不可以修改。
真的需要改变属性的值,那么可以使用命名元组实例的 _replace() 方法, 它会创建一个全新的命名元组并将对应的字段用新的值取代。
>>> s = s._replace(shares=75)
>>> s
Stock(name='ACME', 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 stock_prototype._replace(**s)
使用方法:
  >>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> 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'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
>>>


18、转换并同时计算数据

计算平方和:
nums = [1, 2, 3, 4, 5]
s = sum(x * x for x in nums)   形成一个生成器
聚集函数:
max   min  sum

19、合并多个字典或映射

合并两个字典:
a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
两个字典中执行查找操作(比如先从 a 中找,如果找不到再在 b 中找)。 一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。
from collections import ChainMap
c = ChainMap(a,b)
print(c['x']) # Outputs 1 (from a)
print(c['y']) # Outputs 2 (from b)
print(c['z']) # Outputs 3 (from a)
讲解:
一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后,这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表 并重新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的
>>> len(c)
3
>>> list(c.keys())
['x', 'y', 'z']
>>> list(c.values())
[1, 2, 3]
>>>
如果出现重复键,那么第一次出现的映射值会被返回。 因此,例子程序中的 c['z'] 总是会返回字典 a 中对应的值,而不是 b 中对应的值。

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

它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。 同时,如果原字典做了更新,这种改变不会反应到新的合并字典中去。比如:

>>> a['x'] = 13
>>> merged['x']
1
使用chainMap的优点:
ChainMap 使用原来的字典,它自己不创建新的字典。所以它并不会产生上面所说的结果,比如:

>>> a = {'x': 1, 'z': 3 }
>>> b = {'y': 2, 'z': 4 }
>>> merged = ChainMap(a, b)
>>> merged['x']
1
>>> a['x'] = 42
>>> merged['x'] # Notice change to merged dicts
42
>>>
posted @ 2019-08-28 09:25  Sunny_Boy_H  阅读(325)  评论(0编辑  收藏  举报