堆棧(Stack)結(jié)構(gòu)在迭代中的運(yùn)用

目錄:

1. 前言
2. 棧機(jī)構(gòu)的概覽
3. 應(yīng)用案例及分析
4. 局限性

1.前言

前段時間寫了幾個迭代的腳本,有感而發(fā),于是打算系統(tǒng)的記錄一下關(guān)于這方面的思考。順便做一個中二的搬運(yùn)工~


在這里插入圖片描述

迭代算法我們經(jīng)常會遇到,它本身并不難,通俗講就是重復(fù)反饋過程。下面是一個簡單的Java迭代,每個元素+1,重復(fù)反饋。

 /* 建立一個數(shù)組 */
 int[] integers = {1, 2, 3, 4,  5};
 /* 開始遍歷 */
 for (int j = 0; j < integers.length; j++) {
     int i = integers[j]+1;
     System.out.println(i);
 }

無論什么情況,總會有最直接的辦法,遍歷每一個元素(類似窮舉法)。但是當(dāng)我們對時間復(fù)雜度O有所要求時候,就會去想辦法加快這一進(jìn)程的運(yùn)算效率。下面講述的棧結(jié)構(gòu)是我自己比較習(xí)慣運(yùn)用,在迭代處理中相對效率較高的方法。當(dāng)然會有其他更多種方法,希望多多留言大家一起進(jìn)步!


2. 棧結(jié)構(gòu)的概述

http://www.itdecent.cn/p/eade026ffaf5

上面鏈接是比較詳細(xì)的講解,作為搬運(yùn)工的我,來畫畫重點~
1. 理解: 這就好比食堂大媽收盤子,她不會一個一個收,而是落在一起,一堆一堆收,快?。ó?dāng)然有局限性,最后會講到。)
2. 它是一種常見的受限的線性結(jié)構(gòu)。什么限制呢?只允許在一端插入和刪除數(shù)據(jù)。
這個也好理解,一堆100個盤子落在一起,在中間第38個插一個盤子...此時食堂大媽表情凝重!大喊"臣妾做不到??!"
3. LIFO(last in first out)后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)
聽起來很像會計里的存貨后進(jìn)先出法,先進(jìn)先出法....體現(xiàn)了謹(jǐn)慎性原則。好的吧,這里木有謹(jǐn)慎性原則,不像會計LIFO、FIFO,在期間中不同的采購價格變化,操控存貨賬面價值。這里只是棧的特點,就像子彈上膛123,打出來永遠(yuǎn)是321。
(后面有案例給大家展示)
4. 寫python的同學(xué)們,慶幸沒有指針!所以,后面案例展示大家怎樣打出132,231, 213...的子彈順序


3. 應(yīng)用案例分析(附代碼)

此文中案例,以財務(wù)中計算固定資產(chǎn)Depreciation與NBV為例。當(dāng)然其實運(yùn)用的地方有很多。
在開始前,先附上一個從MySql查詢,讀取,存入,刪除數(shù)據(jù)的方法,自己搬運(yùn)整理的簡化版,玩玩就好。具體如何配置本地電腦裝Mysql,推薦wampserver64, 上學(xué)時候總用,還附帶PHPadmin,一鍵式安裝,安利下~!

http://www.wampserver.com/

"""
MySQL存取過程
"""

class MysqlHelper:
    def __init__(self, host, user, passwd, port, db):
        self.host = host
        self.user = user
        self.passwd = passwd
        self.port = port
        self.db = db
    def open(self):
        self.conn = connect(host=self.host,
                            user=self.user,
                            passwd=self.passwd,
                            port=self.port,
                            db=self.db,)
        self.cursor = self.conn.cursor()
    def close(self):
        self.cursor.close()
        self.conn.close()
    def cud(self, sql):
        try:
            self.open()
            self.cursor.execute(sql)
            self.conn.commit()
            self.close()
        except Exception as e:
            print(e)
    def all(self, sql):
        try:
            self.open()
            self.cursor.execute(sql)
            result = self.cursor.fetchall()
            self.close()
            return result
        except Exception as e:
            print(e)
            
#寫入數(shù)據(jù)到數(shù)據(jù)庫中
def InsertData(dataframe, table):
    if not dataframe.empty:

        host = 'XXXX'
        port = 3306
        user = 'Taylor'
        password = '123'
        schema = 'test'
        paramdict = {'dbuser': user,'dbpwd':password, 'dbip':host, 'dbport':port, 'dbname': schema}
        connect_info = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(paramdict['dbuser'], paramdict['dbpwd'], paramdict['dbip'], paramdict['dbport'], paramdict['dbname'])  # 1
        engine = create_engine(connect_info)
        con = engine.connect()
        dataframe.to_sql(name=table, con=con, if_exists='append', index=False)
        engine.dispose()
        
##刪除數(shù)據(jù)
def Delete_From(Table=None):
    #打開數(shù)據(jù)庫鏈接
    db = pymysql.connect('XXXX', 'Taylor', '123', 'test')

    # 使用cursor()方法獲取操作游標(biāo)
    cursor = db.cursor()

    # SQL語句更新數(shù)據(jù)
    sql = """DELETE FROM %s"""%(Table)

    try:
        # 執(zhí)行SQL語句
        cursor.execute(sql)
        # 提交到數(shù)據(jù)庫執(zhí)行
        db.commit()
        print("刪除數(shù)據(jù)成功")

    except Exception as e:
        print("刪除數(shù)據(jù)失?。篶ase%s"%e)
        #發(fā)生錯誤時回滾
        db.rollback()

    finally:
        # 關(guān)閉游標(biāo)連接
        cursor.close()
        # 關(guān)閉數(shù)據(jù)庫連接
        db.close()
"""
(*)查詢,插入,刪除
"""
##查詢讀取數(shù)據(jù)
sql1 = "Select * from I_love_taylor"
df_1 = MysqlHelper('XXX', 'Taylor', '123', 3306, 'test').all(sql1)
##插入算好的數(shù)據(jù)
InsertData(df_Final, 'I_love_taylor')
##刪除數(shù)據(jù)
Delete_From(Table='I_love_taylor')

案例要求1:

截止至2016年6月30日,某企業(yè)有10萬條固定資產(chǎn),要求在下一年期間,每個月對其計提折舊,并計算出當(dāng)月的NBV, 采用直線法。
已知條件:每條資產(chǎn)的(1)固定資產(chǎn)原值;(2)預(yù)計可使用壽命;(3)剩余可使用壽命;(4)2016年6月30日NBV。
直線法公式:Dep = Cost/Total Life
預(yù)計凈殘值:假設(shè)為0

為了能夠更加清晰的展示,這是最簡單的一種情況,現(xiàn)實中我們還會遇到加速折舊,會遇到凈殘值不同的組合,遇到新增/減少固定資產(chǎn)的情況,但是那些只需套用邏輯篩選即可。
傳統(tǒng)Pythonic做法
這是最直接,但是也是最不推薦的做法,因為很慢。
將所有數(shù)據(jù)裝進(jìn)一個dataframe, 然后復(fù)制出所要計算的期間,遍歷每一條資產(chǎn)的每一個期間,分別計算其折舊和NBV。這樣的方法穩(wěn)定,但是效率過低。
讀取的源數(shù)據(jù):

讀取的元數(shù)據(jù)

for index in df_1.index:
    df_temp=df_1.iloc[index].copy()
    ...........

后面不用寫了,謹(jǐn)記永遠(yuǎn)不要在dataframe用loc遍歷,Loc的本質(zhì)是location, 相當(dāng)耗時,運(yùn)氣好,電腦可能不會崩。如果沒有特別需要,要把某個指揪出來,不要用Loc。即便要用,我們最好要是通過做字典dic,或者json的方式。
升級版pandas內(nèi)置函數(shù)
首先,每一條資產(chǎn)copy,復(fù)制成需要計算的12個月期間,再迭代計算。

##添加n列:將period放在列上
class add_row_col():
    def __init__(self, dataframe, new_col_horizontal, new_col_vertical, counts=None):
        self.d = dataframe
        self.h = new_col_horizontal
        self.n = new_col_vertical
        self.c = counts

    # 創(chuàng)建一個p1---p?的字符串,逗號間隔。
    def create_str(self):
        P_list = list()
        for i in range(1, self.c + 1):
            N = str(str(self.h)) + str(i)
            P_list.append(N)
        PStr = ','.join(P_list)
        return PStr

    # 創(chuàng)建P1-P12的list,增加每個FA的維度。(12*N行)
    def create_matrix(self):
        P_list = list()
        for i in range(1, self.c + 1):
            N = str(str(self.h)) + str(i)
            P_list.append(N)
        return [a for a in P_list]

    # 增加維度?。◤?fù)制N行)
    def multi_dimension(self):
        str_list = self.create_str()
        df_1 = self.d
        df_1.reset_index(drop=True, inplace=True)
        df_1[str(self.n)] = str(str_list)
        df_temp = df_1[str(self.n)].str.split(',', expand=True)
        df_temp = df_temp.stack()
        df_temp = df_temp.reset_index(level=1, drop=True)
        df_temp = pd.DataFrame(df_temp, columns=[str(self.n)])
        df_2 = df_1.drop([str(self.n)], axis=1).join(df_temp)
        df_2[str(self.n)] = df_2[str(self.n)].apply(lambda x: x.replace("'", ""))
        df_2.reset_index(drop=True, inplace=True)
        return df_2

        # 增加維度?。◤?fù)制N列)

    def create_col(self):
        df = self.d.copy()
        matrix = self.create_matrix()
        for g in matrix:
            df[g] = 0
        return df

df_2=add_row_col(df_1, 'Period', 'Period_list', counts=12).multi_dimension()

通過上述方法可以得到以下新dataframe:


在這里插入圖片描述

題外話,如果說我不想Period1,2,3,4,5,6....想要以具體時間來判定怎么辦?那就需要加一個新字段規(guī)定起始日期2016年7月1日,時間戳,定義結(jié)尾日期2017年6月30日。通過以下算法,來處理,即計算間隔。以下不多做說明。

def get_month_range(start_day,end_day):
    months = (end_day.year - start_day.year)*12 + end_day.month - start_day.month
    month_range = ['%s-%s-%s'%(start_day.year + mon//12,mon%12+1,'1') for mon in range(start_day.month-1,start_day.month + months)]
    return month_range

那么傳統(tǒng)方法,核心的迭代就是以下算法:

def iteration_cal(dataframe, new_col, Period_col, Begining_col, cal_col, num_of_period=None, Begining_Period=None):
    df = dataframe
    df[str(new_col)] = 0
    iter_count = int(num_of_period - Begining_Period + 1)
    count = 0
    for i in range(0, iter_count):
        if count < iter_count:
            df[str(new_col)] = df.apply(lambda x: float(x[str(Begining_col)]) - float(x[str(cal_col)])
            if x[str(Period_col)][1:] == str(Begining_Period)
            else (df.loc[x.name - 1, str(new_col)] - x[str(cal_col)]
                  if (x.name + 1) % num_of_period > Begining_Period or (x.name + 1) % num_of_period == 0
                  else 'Pending'), axis=1)
            count += 1
    return df

這是一套比較通用的算法,做一個圖比較好理解。

在這里插入圖片描述

它其實就是每個資產(chǎn)12期規(guī)定為一個模塊,規(guī)定一個起始期限如7,10000個資產(chǎn)對應(yīng)10000個模塊,每個模塊同時迭代,迭代多少以count虛數(shù)為定義。當(dāng)時寫完這段,覺得大事可定,輕松了很多。直到跑全量數(shù)據(jù)后,才意識到這個方法依舊很慢。慢就慢在依舊還要在自己定義的模塊里通過虛數(shù)遍歷。
棧結(jié)構(gòu)stack
下面重點介紹它。stack()會很快,多快呢?線性效率,每增加10倍數(shù)據(jù),時間只增加8倍。前兩種方法算10000條記錄分別是10分鐘,1分鐘。棧結(jié)構(gòu)只要4秒。
核心思想:將需要計算的都放在列,因此需要上面寫的那個class:add_row_col 創(chuàng)建多個列,對列循環(huán)運(yùn)用eval或者apply(lambda公式)。在將每一個結(jié)算好的結(jié)果,當(dāng)作一落落得盤子,放在原始數(shù)據(jù)里。

df_1['Dep_Amount']=df_1.eval('Cost/Total_Life')
df_2 = add_row_col(df_1, 'Depr_M', 'Period_list', counts=12).create_col()
        df_2['Remaining_Life'] = df_2['Remaining_Life'].astype(int)
        ##判斷remaining month 得到default dep
        ##此處需要斷定scenario 對應(yīng)的remaining month
        for i in range(1, 13):
            df_2['Depr_M'+str(i)] = df_2.apply(lambda x: 0 if x['Remaining_Life']-i <0 else x['Dep_Amount'], axis=1)
df_3=add_row_col(df_2, 'NBV_M', 'Period_list', counts=12).create_col()
        df_3['NBV_M1']=df_3['NBV_Beginning']-df_3['Depr_M1']
        for i in range(1, 12):
            df_3['NBV_M'+str(i+1)]=df_3.apply(lambda x: x['NBV_M'+str(i)]-x['Depr_M'+str(i+1)], axis=1)

開始裝盤子!!

##開始裝盤子
df_temp_dep=df_3[[所有dep]]
df_temp_nbv=df_3[[所有nbv]]
df_temp_dep=df_temp_dep.stack()
df_temp_nbv=df_temp_nbv.stack()
df_temp_dep=df_temp_dep.reset_index(level=1, drop=True)
df_temp_nbv=df_temp_nbv.reset_index(level=1, drop=True)
df_temp_dep=pd.DataFrame(df_temp_dep, columns=['dep'])
df_temp_nbv=pd.DataFrame(df_temp_nbv, columns=['nbv'])
df_1=df_1.drop(columns=[沒用得新加字段])
df_1=df_1.join(df_temp_dep)
df_1['nbv']=..........
##注意,這里join默認(rèn)按照索引join。
df_1。reset_index(drop=True,inplace=True)

大致核心就是這樣,具體有很多細(xì)節(jié),可以自行修改。stack可以做很多,只要是類似beginning-thisamount=ending得滾動迭代,都可以用棧結(jié)構(gòu)。

4. 局限性

最后講講局限性。就像之前提到得,它是后進(jìn)先出,受限得線性結(jié)構(gòu)。那么就會導(dǎo)致,無法個別差異計算。未來將這些落起來得盤子,也只能按順序擺放。如果是做數(shù)據(jù)模型得同學(xué),前期一定做好聚類,否則就杯具了。其他用途得,如只是的單純得財務(wù),人力管理,為了想讓它打出231, 132等不同順序得子彈,我們需要再做一個引用多維度得包。通過唯一主鍵,對其進(jìn)行重新排序。另外一個問題,就是它占用空間較大,且對索引得排列要求較高。這也就意味著,一旦你發(fā)現(xiàn)數(shù)據(jù)沒有算全,那就從頭開始落吧!

And......

如果有何錯誤,請大家多多指正。如果有其他方法,希望分享,共同進(jìn)步~!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容