1. 程式人生 > >python死磕一之數據結構和基礎庫

python死磕一之數據結構和基礎庫

數值 val for循環 upd values als lambda表達式 += hist

  昨天在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: cant 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)

python死磕一之數據結構和基礎庫