數據結構和算法

 

Python內置數據結構:python

  列表list、集合set、字典dict。本文針對各類數據結構的提出搜索,排列、以及篩選等這一類常見的問題的解決方案。算法

 

1、將序列分解爲單獨的變量(分解操做)api

  任何的元組、序列或可迭代對象均可以經過一個簡單的賦值操做來分解爲單獨的變量。(包括字符串、文件、迭代器以及生成器)數據結構

  惟一要求就是變量的總數和結構要與序列相吻合。app

  >>> p = (4,5)函數

  >>> a, b = p 性能

  當作分解操做時,有時候可能想丟棄某些特定的值。Python並無提供特殊的語法來實現這一點,可是能夠選一個用不到的變量名,做爲要丟棄的值得名稱。優化

  >>> data = ['Iphone', 50, 91.1, (2019, 9, 9) ]ui

  >>> _, shares, price, _  = dataspa

 

2、從任意長度的可迭代對象中分解元素

  從某個可迭代對象中分解出N個元素,可是這個可迭代對象的長度可能超過N,致使出現,分解的值過多的異常。

  使用 *表達式 解決這個問題。如去掉第一個和最後一個,留下中間計算平均值。

  >>> record = ('Tom', '442994909@qq.com', '188xxxxxxxx', '020xxxxxxxxx')

  >>> name, email, *phone_number = record

  由*表達式 修飾的變量能夠位於列表的任意位置。

  (1)能夠和拆分操做 split,相結合使用。

  >>> line = 'firefly:*:2:2:root:/usr/bin:/usr/bin/empty'

  >>> uname, *fields, homedir, sh = line.splt(':')

  (2)分解出值而後丟棄。

  >>> data = ['Iphone', 50, 91.1, (2019, 9, 9) ]

  >>> name, *_, (year, *_) = data

  (3)具備拆分功能的函數,實現遞歸算法。

  >>> def sum(item):

  ...    head, *tail = items

  ...    return head + sum(tail) if tail else head

   return head + sum(tail) if tail else head 表示: return tail 或者 head 或者 兩個值同時 return,注意與 return head + ( sum(tail) if tail else head )區別

 

3、保存最後N個元素

  使用deque(maxlen=N),建立一個固定長度的隊列。當有新記錄加入而隊列已滿時會自動移除最老的那條記錄。

from collections import deque

def search_match(f,pattern,history=5):
    previous_lines = deque(maxlen=history)
    for line in f:
        if pattern in line:
            yield line,previous_lines
        previous_lines.append(line)

with open('re.txt','w') as f:
    for line, previouos_lines in search_match(f,'python',5):
        for pline in previouos_lines:
            print(pline,end='')
        print(line,end='')
        print('-'*20)

  若是不指定隊列的大小,也就獲得一個無界限的隊列,能夠在兩端執行添加和彈出操做。

  >>> q  = deque()

  >>> q.append(1)

  >>> q.appendleft(4)

  >>> q.pop()

  >>> q.popleft()

  從隊列兩端添加或彈出元素的複雜度都是O(1),這和列表不一樣,當從列表的頭部插入或移除元素時,列表的複雜度爲O(N)。

 

4、找到最大最小的N個元素

  在集合中找出最大或最小的N個元素。

  heapq模塊中有兩個函數:nlargest()和nsmallest(),這兩個函數均可以接受一個key,從而容許它們工做在更加複雜的數據結構之上。

  cheap = heapq.nsmallest(3, portfolio, key=lambda s:s['price'])

  expensive = heapq.nlargest(3, portfolio, key=lambda s:s['price'])

  (1)當尋找的最大最小N個元素,在總數目中相比,N很小時,那麼鞋棉函數能夠提供更好的性能。

  >>> heap = list(nums)

  >>> heapq.heapify(heap)

  這個函數會在底層將數據轉化成列表,且元素會以堆的順序排序。

  堆最重要的特性就是heap[0]老是最小那個的元素。

  接下來的元素能夠經過heapq.heappop()方法獲取。複雜度爲O(logN),N表明堆的大小。

  (2)當要找的元素數量相對較小時,函數nlargest()和nsmallest()纔是最適用的。

  (3)若是隻是簡單找最大最小元素(N=1)時,那麼用min()和max()會更加快。

  (4)若是N和集合自己的大小差很少大,更快的方法是對集合排序,而後作切片操做。sorted(items)[:N]或sorted(items)[-N:]

  (5)函數nlargest()和nsmallest()會根據使用方式的不一樣,自動作出一些優化措施。

 

5、實現優先級隊列

  實現一個隊列,能以給定的優先級來對元素排序,其每次pop操做時都會返回優先級最高的那個元素。

  利用heapq模塊實現一個簡單的優先級隊列:

import heapq
class PriorityQueue:

    def __init__(self):
        self._queue = []
        self._inedx = 0

    def push(self,item,priority):
        heapq.heappush(self._queue,(-priority,self._inedx,item))
        self._inedx += 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)

p = PriorityQueue()
p.push(Item('Maria'),3)
p.push(Item('Lilly'),5)
p.push(Item('David'),7)

print(p.pop())    #Item('David')

  (1)函數heapq.heappush()以及heapq.heappop()分別實現將元素從列表_queue中插入和移除,且保證列表中第一個元素的優先級最低。

  (2)heappop()方法老是返回「最小」的元素。

  (3)隊列以元組(-priority, index, item)的形式組成。把priority取負值是爲了讓隊列可以按元素的優先級從高到低的順序排列。

  (4)通常狀況下,堆是按照從小到大的順序排序的。

  (5)變量index做用是爲了將具備相同優先級的元素以適當的順序排列。元素將按照入隊列時的順序來排序。

 

6、在字典中將鍵映射到多個值上

  將一個key映射到多個值得字典上,(一鍵多值字典)。

a = {
    'a':[1,2,3],
    'b':[4,5]
}
b = {
    'c':{5,7,9},
    'd':{2,3,4}
}

  須要將多個值保存到另外一個容器如列表或集合中,爲了方便建立這樣的字典,可使用collections.defaultdict類,省去初始化時建立空列表,空集合的麻煩。

  d = collections.defaultdict(list)

  d = collections.defaultdict(set)

  d['a'].append(1)

  或者使用普通字典上調用setdefault()方法來取代。

  d = {}

  d.setdefault('a', []).append(1)

 

7、讓字典保持有序

  使用collections.OrderedDict類。

  d = collections.OrderedDict()

  d['a'] = 1

  OrderedDict類內部維護了一個雙向鏈表,它會根據元素加入的順序來排列鍵的位置。

  第一個新加入的元素被放置在鏈表的末尾。接下來對已存在的鍵作從新賦值不會改變鍵的順序。

  OrderedDict的大小是普通字典的2倍多,這是因爲它額外建立的鏈表所致的。

 

、與字典有關的計算(最大、最小、排序)

  爲了對字典內容作些有用的計算,一般會利用 zip()將字典的 key和 value反轉過來。

  min_price = min(zip(prices_dict.values(), prices.keys()))

  max_price = max(zip(prices_dict.values(), prices.keys()))

  prices_sorted = sorted(zip(prices_dict.values(), prices.keys()))

  ▲ zip()建立了一個迭代器,它的內容只能被消費一次。

 

9、在兩個字典中尋找相同

  以下兩個字典

a = {
    'x':1,
    'y':2,
    'z':3,
}
b= {
    'w':10,
    'x':11,
    'y':2,
}

  只需經過keys()和items()方法執行常見的集合操做便可。

  a.keys() & b.keys()      # { 'x', 'y' }

  a.keys() - b.keys()       # { 'z' }

  a.items() & b.items()   # { ('y', 2) } 

  修改或者過濾字典中的內容。

  c = {key:a[key] for key in a.keys() - {'z', 'w'} }

  

10、從序列中移除重複項且保持元素間順序不變

  若是序列中的值是可哈希的,那麼這個問題能夠經過使用集合和生成器輕鬆解決。

def dedupe(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

  若是想在不可哈希的對象序列中去除重複項。

def dedupe(items,key=None):
    seen = set()
    for item in items:
        val = key 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},]
print(list(dedupe(a,key=lambda x:(x['x'],x['y']))))
# [{'y': 2, 'x': 1}, {'y': 3, 'x': 1}, {'y': 4, 'x': 2}]

  若是想讀一個文件,去除其中重複的文本行,能夠只需這樣處理:

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

 

11、對切片命名

  內置的slice()函數會建立一個切片對象,能夠用在任何容許進行切片操做的地方。

>>> items = [0,1,2,3,4,5,6]
>>> a = slice(2,4)
>>> items[2:4]
>>> items[a]
[2, 3]

  經過使用indices(size)方法將切片映射到特定大小的序列上。返回一個(start, stop, step)元組。

>>> s = 'HelloWorld'
>>> a.indices(len(s))
(2, 4, 1)
>>> for i in range(*a.indices(len(s))):
...     print(s[i])
l
l

 

12、找出序列中出現次數最多的元素

  使用collections.Counter類,其中most_common()方法能直接獲得答案。

  >>> word_counts = collections.Counter(words)

  >>> top_three = word_counts.most_common(3)

  手動增長計,經過自增或者update()方法:

  word_counts[word] += 1

  word_counts.update(morewords)

  Counter對象還支持同各類數學運算操做結合使用。

  >>> a = Counter(words)

  >>> b = Counter(morewords)

  >>> c = a + b

 

十3、經過公共鍵對字典列表排序

  operator.itemgetter函數對這類結構進行排序是很是簡單的。

  >>> from operator import itemgetter

  >>> rows_by_flname = sorted(rows, key=itemgetter('frame', 'lname'))

  >>> rows_by_uid = sorted(rows, key=itemgetter('uid'))

  >>> min(rows, key=itemgetter('uid'))

  >>> max(rows, key=itemgetter('uid'))

  一樣,使用lambda表達式也能夠。

  >>> rows_by_flname = sorted(rows, key=lambda x: (x['fname'], x['lname']))

  >>> rows_by_uid = sorted(rows, key=lambda x:x['uid'])

  

十4、對不原生支持比較操做的對象排序

  若是應用中有一系列的User對象實例,而咱們想經過user_id屬性來對他們排序,則能夠提供一個可調用對象將User實例做爲輸入而後返回user_id。

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(12),User(33)]
a = sorted(users, key=lambda x:x.user_id)

  或者使用operator.itemgetter()

 

十5、根據字段將記錄分組

  有一系列的字典或對象實例,咱們想根據某個特定的字段(如日期)來分組迭代數據。

  itertools.groupby()函數對數據進行分組。groupby()建立一個迭代器,每次迭代返回一個value和子迭代器sub_itertor,子迭代器產生全部該分組內的項。

  (1)先進行排序:

  >>> rows.sort(key=itemgetter('date'))

  (2)而後分組:

for date, item in itertools.groupby(rows, key=itemgetter('data')):
    print(data)
    for i in item:
        print(i)

  若是隻分組不須要排序,創建一個一鍵多值字典便可。

rows_by_date = collections.defaultdict(list)
for row in rows:
    rows_by_date[row['date']].append(row)

 

十6、篩選序列中元素

  (1)列表推導式:

    n = [n for n in my_list if n > 0]

  (2)生成器表達式:

    n = (n for n in my_list if n > 0)

  (3)filter() 函數處理:

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False
    
ivals = list(filter(is_int,my_list))

  (4)新值替換舊值

    n = [n if n > 0 else 0 for n in my_list]

  (5)itertools.compress(),接受一個可迭代對象和一個布爾選擇器序列做爲輸入。輸出,在布爾序列中爲True的可迭代對象元素。

>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, True, False]
list( itertools.compress(addresses, more5) )

 

十7、從字典中提取子集

  利用字典推導式:

  p1 = { key:value for key, value in prices.items() if value > 100 } 

  p2 = { key:value for key, value in prices.items() if key in tech_name }

 

十8、將名稱映射到序列的元素中

  經過名稱來訪問元素,減小結構中對位置的依賴性。

  相比普通的元組,collections.namedtuple()。命名元組只增長了極小的開銷就提供了這些便利。

  實際上collections.namedtuple()是一個工廠方法,他返回的是Python中標準元組類型的子類。

  >>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])

  >>> sub = Subscriber('442994909@qq.com', '2020-2-2')

  >>> addr, joined = sub

  命名元組的主要做用在於將代碼同它所控制的元素位置間解耦。

  同時還做爲字典的替代,可是namedtuple是不可變的。

  能夠經過_replace()方法來實現修改,會建立一個全新的命名元組,並對相應的值作替換。

  >>> s = s._replace(addr='xxx@qq.com')

  _replace(),還能夠做爲一種簡便的方法填充具備可選或缺失字段的命名元組。

stock_prototype = Stock('', 0, 0.0, None, None)
def dict_to_stock(s):
    return stock_prototype._replace(**s)

 

二10、將多個映射合併爲單個映射

  想執行查找操做,必須得檢查這兩個字典,一種簡單的方法時利用collections.ChainMap類來解決這個問題。

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

  ChainMap可接受多個映射而後在邏輯上使它們表現爲一個單獨的映射結構。

  這些映射在字面上並不會合併在一塊兒。相反,ChainMap只是簡單得維護一個記錄底層映射關係的列表,而後從新定義常見的字典操做來掃描這個列表。

  若是有重複的鍵,那麼這裏會採用第一個映射中所對應的值。

  ChainMap與帶有做用域的值,全局變量,局部變量一塊兒使用時特別有用。

>>> values = ChainMap()
>>> values['x'] = 1
>>> values = values.new_child()
>>> values['x'] = 2
>>> values = values.new_child()
>>> values['x'] = 3
>>> values
ChainMap({'x': 3}, {'x': 2}, {'x': 1})
>>> values['x']
3
>>> values = values.parents
>>> values['x']
2
>>> values = values.parents
>>> values['x']
1
>>> values
ChainMap({'x': 1})

  (2)利用字典的update()方法將多個字典合併在一塊兒。

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

  須要單獨構建一個字典,若是其中任何一個原始字典做了修改,這個改變不會反應到合併後的字典中。!

  而ChainMap使用的就是原始的字典。

相關文章
相關標籤/搜索