1. 程式人生 > >Python第五周 學習筆記(2)

Python第五周 學習筆記(2)

學習筆記

裝飾器應用練習

一、實現一個cache裝飾器,實現可過期被清除的功能

  • 簡化設計,函數的形參定義不包含可變位置參數、可變關鍵詞參數和keyword-only參數
  • 可以不考慮緩存滿了之後的換出問題

    1)原始

def cache(fn):
    import inspect

    local_cache = {}

    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)
        params = sig.parameters

        param_names = list(params.keys())
        temp_dict = {}

        #處理位置參數
        for k, v in enumerate(args):
            temp_dict[param_names[k]] = v

        #更新關鍵字參數值
        temp_dict.update(kwargs)

        #更新默認值
        for k, v in params.items():
            temp_dict[k] = v.default

        #排序生成元組
        temp_tuple = tuple(sorted(temp_dict.items()))

        if temp_tuple not in local_cache.keys():
            local_cache[temp_tuple] = fn(*args, **kwargs)

        return local_cache[temp_tuple]
    return wrapper

import time
@cache
def add(x, y, z): 
    time.sleep(2)
    return x + y + z

2)加入過期判斷

import inspect
from datetime import datetime
def cache(duration):
    def _cache(fn):
        local_cache={}

        def wrapper(*args, **kwargs):

            def expire_cache(cache:dict):
                expire_list = []

                for k,(_,stamp)  in cache.items():
                    delta = (datetime.now().timestamp() - stamp)

                    if delta > duration:
                        expire_list.append(k)

                for k in expire_list:
                    cache.pop(k)

            expire_cache(local_cache)

            sig=inspect.signature(fn)
            params=sig.parameters

            param_names=list(params.keys())
            param_dict={}

            for k,v in enumerate(args):
                param_dict[param_names[k]] = v

            param_dict.update(kwargs)

            for k, v in params.items():
                if k not in param_dict.keys():
                    param_dict[k] = v.default

            param_keys=tuple(sorted(param_dict.items()))

            if param_keys not in local_cache.keys():
                local_cache[param_keys]=(fn(*args,**kwargs), datetime.now().timestamp())

            return local_cache[param_keys][0]
        return wrapper
    return _cache

二、寫一個命令分發器

  • 程序員可以方便的註冊函數到某一個命令,用戶輸入命令時,路由到註冊的函數
  • 如果此命令沒有對應的註冊函數,執行默認函數
  • 用戶輸入用input(">>")
def cmd_dispatcher(): #封裝
    cmd_dict = {}

    def reg(cmd):
        def _reg(fn):
            cmd_dict[cmd] = fn
            return fn
        return _reg

    @reg(‘default_func‘)
    def default_func():
        print(‘default‘)
        return

    def dispatcher():
        while True:
            cmd = input(‘>>‘)
            if cmd == ‘quit‘:
                return
            cmd_dict.get(cmd, default_func)()

    return reg, dispatcher #封裝

reg, dispatcher = cmd_dispatcher() #封裝&解構

@reg(‘add‘)
def add(): #add=reg(‘add‘)(add)
    print(1)
    return

dispatcher()

二叉樹遍歷


廣度優先遍歷

  • 層序遍歷,按照樹的層次,從第一層開始,自左向右遍歷元素

    深度優先遍歷

  • 設樹的根結點為D,左子樹為L,右子樹為R,且要求L一定在R之前,則有下面幾種遍歷方式:
    • 前序遍歷,也叫先序遍歷、也叫先根遍歷,DLR
    • 中序遍歷,也叫中根遍歷,LDR
    • 後序遍歷,也叫後根遍歷,LRD
  • 遍歷序列:將樹中所有元素遍歷一遍後,得到的元素的序列。將層次結構轉換成了線性結構

前序遍歷DLR

  • 從根結點開始,先左子樹後右子樹
  • 每個子樹內部依然是先根結點,再左子樹後右子樹。遞歸遍歷
  • 遍歷序列
    • A BDGH CEIF

中序遍歷LDR

  • 從根結點的左子樹開始遍歷,然後是根結點,再右子樹
  • 每個子樹內部,也是先左子樹,後根結點,再右子樹。遞歸遍歷
  • 遍歷序列
    • 左圖
    • GDHB A IECF
    • 右圖
    • GDHB A EICF

後序遍歷LRD

  • 先左子樹,後右子樹,再根結點
  • 每個子樹內部依然是先左子樹,後右子樹,再根結點。遞歸遍歷
  • 遍歷序列
    • GHDB IEFC A

堆排序


堆Heap

  • 堆是一個完全二叉樹
  • 每個非葉子結點都要大於或者等於其左右孩子結點的值稱為大頂堆
  • 每個非葉子結點都要小於或者等於其左右孩子結點的值稱為小頂堆
  • 根結點一定是大頂堆中的最大值,一定是小頂堆中的最小值

大頂堆

  • 完全二叉樹的每個非葉子結點都要大於或者等於其左右孩子結點的值稱為大頂堆
  • 根結點一定是大頂堆中的最大值

小頂堆

  • 完全二叉樹的每個非葉子結點都要小於或者等於其左右孩子結點的值稱為小頂堆
  • 根結點一定是小頂堆中的最小值

理論實現

1、構建完全二叉樹

  • 待排序數字為 30,20,80,40,50,10,60,70,90
  • 構建一個完全二叉樹存放數據,並根據性質5對元素編號,放入順序的數據結構中
  • 構造一個列表為[0,30,20,80,40,50,10,60,70,90]

2、構建大頂堆——核心算法

  • 度數為2的結點A,如果它的左右孩子結點的最大值比它大的,將這個最大值和該結點交換
  • 度數為1的結點A,如果它的左孩子的值大於它,則交換
  • 如果結點A被交換到新的位置,還需要和其孩子結點重復上面的過程
2.1 構建大頂堆——起點結點的選擇
  • 從完全二叉樹的最後一個結點的雙親結點開始,即最後一層的最右邊葉子結點的父結點開始結點數為n,則起始結點的編號為n//2(性質5)
2.2 構建大頂堆——下一個結點的選擇
  • 從起始結點開始向左找其同層結點,到頭後再從上一層的最右邊結點開始繼續向左逐個查找,直至根結點
2.3 大頂堆的目標
  • 確保每個結點的都比左右結點的值大

3、排序

  • 將大頂堆根結點這個最大值和最後一個葉子結點交換,那麽最後一個葉子結點就是最大值,將這個葉子結點排除在待排序結點之外
  • 從根結點開始(新的根結點),重新調整為大頂堆後,重復上一步

代碼實現

1.打印樹結構(非必要,方便查看每步操作對樹結構的改變)

1)方法一 居中對齊

def show_tree(lst, unit_width=2):
    from math import log2, ceil

    length = len(lst)
    depth = ceil(log2(length + 1))
    width = 2 ** depth - 1
    index= 0

    for i in range(depth):
        for j in range(2 ** i):
            print(‘{:^{}}‘.format(lst[index], width * unit_width), end = ‘ ‘ * unit_width)

            index += 1
            if index >= length:
                break
        width //= 2
        print()

2)方法二 投影柵格實現

from math import ceil, log2

#投影柵格實現
def print_tree(array):
    ‘‘‘
    ‘‘‘
    index = 1
    depth = ceil(log2(len(array)))
    sep = ‘ ‘
    for i in range(depth):
        offset = 2 ** i
        print(sep * (2 ** (depth - i -1) - 1), end = ‘‘)
        line = array[index : index + offset]
        for j, x in enumerate(line):
            print("{:>{}}".format(x, len(sep)), end = ‘‘)
            interval = 0 if i == 0 else 2 ** (depth - i) - 1
            if j < len(line) - 1:
                print(sep * interval, end = ‘‘)

        index += offset
        print()

2.堆排序實現

def heap_sort(lst:list):
    ‘‘‘
    堆排序

    :type lst: list
    :rtype: list
    ‘‘‘
    length = len(lst)
    lst.insert(0,0) # 前插0為了索引和結點號能夠對應上,索引不必加一,便於理解,輸出時切片即可
    def heap_adjust(start, end):
        ‘‘‘
        調整當前節點

        調整結點的起點在n//2,保證所有調整結點都有孩子結點
        :param end: 待比較個數
        :param start: 當前節點下標
        :rtype: None
        ‘‘‘
        while 2 * start <= end: # 如果該結點下還有孩子則進入循環,否則跳出
            lchild_index = 2 * start #該結點號*2 為左孩子結點
            max_child_index = lchild_index #

            if lchild_index < end and lst[lchild_index + 1] > lst[lchild_index]: # 如果有右孩子並且右孩子比左孩子的數大,則更新最大值索引
                max_child_index = lchild_index + 1

            if lst[max_child_index] > lst[start]: #孩子結點比較完後與父結點比較,最大值放到父結點,並且下一次叠代的父結點是本次最大孩子索引
                lst[start], lst[max_child_index] = lst[max_child_index], lst[start]
                start = max_child_index
            else: # 如果父結點最大則直接跳出,因為排頂堆從編號最大的子樹開始調整,即保證了本次最大孩子結點與其孩子結點已經形成了頂堆
                break

    for st in range(length//2, 0, -1): # 調整為大頂堆
        heap_adjust(st, length)

    for end in range(length, 1, -1): #sort排序 根結點與最後結點交換,每次叠代刨除最後結點,直至只剩根結點
        lst[1], lst[end] = lst[end], lst[1]
        heap_adjust(1, end - 1)

    return lst[1:]
  • 時間復雜度O(nlogn),平均時間復雜度O(nlogn)
  • 空間復雜度O(1)
  • 不穩定排序

Python第五周 學習筆記(2)