python死磕一之数据结构和基础库

  昨天在git上看到一篇python基础学习的文章,本以为基础还算好的我,通过文章中的几个例子,发现不会的地方以及日常忽略的重要地方,特以此着手写下这个死磕系列,警醒自己,以及用来及时复习。

  一、解压可迭代对象赋值给多个对象

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)

  遗漏点:for循环中可以解压元素对象,并且自动按位置解压或压缩。我以前只了解for循环可以遍历字典元素的items(),确实没有用过,也没有试过在遍历每一个单个元素的时候可以按位置解压。

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

  遗漏点:用*去接受不定长参数,可以用多个*去解压,如果原变量包含了tuple,接收时可以用()来接受tuple。

  二、在迭代操作或者其他操作的时候,怎样只保留最后有限几个元素的历史记录

  之前思路:在迭代中设置一个计数变量,以及判断条件,达到最后几个次数后将迭代元素放入列表。

  遗漏点:利用三方库collections.deque,当deque(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)

  三、怎样从一个集合中获得最大或者最小的 N 个元素列表

    之前思路:利用sorted排序,k取最后或最前几个

  遗漏点:利用heapq基础库,如果有top问题,可以直接联想到此三方库。heapq.nlargest(count, iterable)  

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

  而且这两个函数,都可以接受关键字参数,用于复杂的数据结构中:

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'])

  迭代portfolio中每一个元素,以之为参数到lambda表达式中,作为key来排序。所以相比于sorted,代码看起来更加优雅和简便。

  四、如果你想在一个集合中依次pop出最小或最大的 N 个元素,并且 N 小于集合元素数量

  之前思路:排序好,迭代压入栈中。

  遗漏点:heapq.heapify(list) 可以在底层为list排序并压入栈中,可以之间pop。

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> heap = list(nums)
>>> heapq.heapify(heap) # heapify方法无返回值,作用是将heap做了堆排序
>>> heap
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
>>> heapq.heappop(heap)
-4
>>> heapq.heappop(heap)
1
>>> heapq.heappop(heap)
2

  五、实现一个优先级队列,并且在这个队列上面每次 pop 操作总是返回优先级最高的那个元素

  之前思路:设定一个方法,参数是要传的元素,以及优先级,如果再严格一点需要传个顺序计数,因为如果优先级相同,可以根据顺序来判断优先传递哪一个。

每次放进队列调用这个方法,初始化按优先级,顺序计数排序,顺序计数是一个全局变量。

  遗漏点:heapq.heappush(self._queue,(priority,item))帮我们封装了一个简单的函数,实现思路其实差不多,计数的话需要我们自己实现

heapq.heappop(self._queue)可以pop出排好序的元素。

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))  # 第一个参数是往哪个队列中push,第二个参数可以传一个数组(优先级,顺序,元素)
        self._index += 1

    def pop(self):
        return heapq.heappop(self._queue)[-1]

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

  之前思路:建立一个字典,值再设立一个可迭代对象。

  遗漏点一:collections.defaultdict(dict or list or set )可以快速实现

  

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)

  遗漏点二:如果进行新元素的插入,需要判定这个key是否存在。用setdefault能很好的解决这个问题

d = {} # 一个普通的字典
d.setdefault('a', []).append(1)  #[]中可以设定一个初始值
d.setdefault('a', []).append(2)
d.setdefault('b', []).append(4)

  七、OrderedDict的原理

  之前我自己只是用过这个有序列表,但是他的原理并没有去深究。其实OrderedDict 内部维护着一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来的时候, 它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。

  注意:一个 OrderedDict 的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。 所以如果你要构建一个需要大量 OrderedDict 实例的数据结构的时候(比如读取 100,000 行 CSV 数据到一个 OrderedDict 列表中去), 那么你就得仔细权衡一下是否使用 OrderedDict 带来的好处要大过额外内存消耗的影响。

  八、怎样在数据字典中执行一些计算操作(比如求最小值、最大值、排序等等)?

  之前思路:最大值最小值都可以用内置max、min函数来求,sort来排序,但是只能取到键或值的其中之一。在根据键的取值会有些麻烦。

  遗漏点:zip函数可以帮我们封装以及调换变量的顺序,因为max,min函数不指定key的条件下,默认是根据第一个参数来排序的,而且我们可以同时取到多个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()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys())) # zip(元素1,元素2)
# max_price is (612.78, 'AAPL')

  注意:zip生成的是一个仅可以访问一次的迭代器,如果超过一次就会报错

print(min(prices_and_names)) # OK
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence

  九、怎样在两个字典中寻找相同点(比如相同的键、相同的值等等)?

    之前思路:同时遍历进行相同元素判断(我承认很蠢。。。)

  遗漏点: & | 符号可以用来代替逻辑关系。很简单而且易读性也强。大家如果以后有这种取交集或者差集应该优先想到有内置的运算符。

a.keys() & b.keys() # { 'x', 'y' }
# Find keys in a that are not in b
a.keys() - b.keys() # { 'z' }
# Find (key,value) pairs in common
a.items() & b.items() # { ('y', 2) }

  十、怎样在一个序列上保持元素顺序的同时消除重复的数值

  之前思路:首先大家都会想到强转成set,不过set返回来的顺序会改变。或者自己定义一个方法:建立一个列表,每次遍历原来的可迭代对象,判断元素是否出现在新建列表中,再进行插入。

  遗漏点:在我们编码中,在直接生成list,tuple,dict时一定要注意,如果数据量非常大的话,可能会导致内存崩溃,所以我们要用yield产生一个生成器,每次迭代生成器中的数据,这样会使我们的方法更加通用。

  十一、如果你的程序包含了大量无法直视的硬编码切片,并且你想清理一下代码。

  之前思路:在我工作的前两个月,没少被骂,因为可读性真是自己可以读懂,甚至过了两三个月再看自己的代码都不知道自己要干什么,所以在代码的可读性上我们一定要站在第三者的角度,比如你是一个项目的接锅侠,你希望代码应该是怎样的才容易看懂。

  遗漏点:在下标表示上,最容出现读不懂,一般用一个读懂的 变量名去封装:

lis = [1,3,4,5,11,21]

max_point_index = slice(1,3)

res = lis[max_point_index]   # max_point_index是个左开右闭区间

print(res)

  十二、怎样找出一个序列中出现次数最多的元素呢?

  之前思路:建立一个字典,元素作为键,计数变量作为值。根据值来排序。

  遗漏点:collections.Counter()实现的就是以上的效果,同时还有个most_common(count)方法,来统计出现top个数

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还可以接update方法,更新字典。

  十三、你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭代访问。

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'},
]

  之前思路:建立列表,遍历rows,取出每个序列的date字段,相同的添加进序列。

  遗漏点: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'},
]

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
  {'date': '07/01/2012', 'address': '5412 N CLARK'}
  {'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012
  {'date': '07/02/2012', 'address': '5800 E 58TH'}
  {'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
  {'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012
  {'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012
  {'date': '07/04/2012', 'address': '5148 N CLARK'}
  {'date': '07/04/2012', 'address': '1039 W GRANVILLE'}

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

  之前思路:可以用functools.filter函数,或者列表解析式,生成器表达式

  遗漏点: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]

如果我们想找出counts大于5对应下标address的值,一般的方法:

link = zip(addresses,counts)
print(link)
for item in link:
    if item[-1] > 5:
        print(item[0])

如果用compress:第一个参数是迭代对象,第二个是迭代规则,返回的是一个迭代器

>>> 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']

  十五:你有一段通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的代码难以阅读, 于是你想通过名称来访问元素。

  遗漏点:collections.namedtuple(),这个方法其实也是有助于我们提高代码可读性的,它其实可以理解作为一个类,我们可以用类名.属性的方法去访问值。

>>> from collections import namedtuple
>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
>>> sub = Subscriber('[email protected]', '2012-10-19')
>>> sub
Subscriber(addr='[email protected]', joined='2012-10-19')
>>> sub.addr
'[email protected]'
>>> sub.joined
'2012-10-19'

这样我们就可以从毫无意义的下标中脱身,增强了代码的可阅读性。命名元组另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间。 如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。 但是需要注意的是,不像字典那样,一个命名元组是不可更改的。

>>> s = Stock('ACME', 100, 123.45)
>>> s
Stock(name='ACME', shares=100, price=123.45)
>>> s.shares = 75
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

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

>>> s = s._replace(shares=75)
>>> s
Stock(name='ACME', shares=75, price=123.45)

  十六、现在有多个字典或者映射,你想将它们从逻辑上合并为一个单一的映射后执行某些操作, 比如查找值或者检查某些键是否存在。

    之前思路:用字典的update逻辑,将多个字典合并为一个,在进行查找。但是如果多个字典有相同的值会被覆盖。

  遗漏点:collections.ChainMap(),方法相当于一个链式列表,不需要合并,如果有相同的键名,会返回第一个查找到的

a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
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)

猜你喜欢

转载自www.cnblogs.com/jimmyhe/p/10799767.html