堆栈(Stack)结构在迭代中的运用

目录:

1. 前言
2. 栈机构的概览
3. 应用案例及分析
4. 局限性

1.前言

前段时间写了几个迭代的脚本,有感而发,于是打算系统的记录一下关于这方面的思考。顺便做一个中二的搬运工~


在这里插入图片描述

迭代算法我们经常会遇到,它本身并不难,通俗讲就是重复反馈过程。下面是一个简单的Java迭代,每个元素+1,重复反馈。

 /* 建立一个数组 */
 int[] integers = {1, 2, 3, 4,  5};
 /* 开始遍历 */
 for (int j = 0; j < integers.length; j++) {
     int i = integers[j]+1;
     System.out.println(i);
 }

无论什么情况,总会有最直接的办法,遍历每一个元素(类似穷举法)。但是当我们对时间复杂度O有所要求时候,就会去想办法加快这一进程的运算效率。下面讲述的栈结构是我自己比较习惯运用,在迭代处理中相对效率较高的方法。当然会有其他更多种方法,希望多多留言大家一起进步!


2. 栈结构的概述

//www.greatytc.com/p/eade026ffaf5

上面链接是比较详细的讲解,作为搬运工的我,来画画重点~
1. 理解: 这就好比食堂大妈收盘子,她不会一个一个收,而是落在一起,一堆一堆收,快!(当然有局限性,最后会讲到。)
2. 它是一种常见的受限的线性结构。什么限制呢?只允许在一端插入和删除数据。
这个也好理解,一堆100个盘子落在一起,在中间第38个插一个盘子...此时食堂大妈表情凝重!大喊"臣妾做不到啊!"
3. LIFO(last in first out)后进先出的数据结构
听起来很像会计里的存货后进先出法,先进先出法....体现了谨慎性原则。好的吧,这里木有谨慎性原则,不像会计LIFO、FIFO,在期间中不同的采购价格变化,操控存货账面价值。这里只是栈的特点,就像子弹上膛123,打出来永远是321。
(后面有案例给大家展示)
4. 写python的同学们,庆幸没有指针!所以,后面案例展示大家怎样打出132,231, 213...的子弹顺序


3. 应用案例分析(附代码)

此文中案例,以财务中计算固定资产Depreciation与NBV为例。当然其实运用的地方有很多。
在开始前,先附上一个从MySql查询,读取,存入,删除数据的方法,自己搬运整理的简化版,玩玩就好。具体如何配置本地电脑装Mysql,推荐wampserver64, 上学时候总用,还附带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)
            
#写入数据到数据库中
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()
        
##删除数据
def Delete_From(Table=None):
    #打开数据库链接
    db = pymysql.connect('XXXX', 'Taylor', '123', 'test')

    # 使用cursor()方法获取操作游标
    cursor = db.cursor()

    # SQL语句更新数据
    sql = """DELETE FROM %s"""%(Table)

    try:
        # 执行SQL语句
        cursor.execute(sql)
        # 提交到数据库执行
        db.commit()
        print("删除数据成功")

    except Exception as e:
        print("删除数据失败:case%s"%e)
        #发生错误时回滚
        db.rollback()

    finally:
        # 关闭游标连接
        cursor.close()
        # 关闭数据库连接
        db.close()
"""
(*)查询,插入,删除
"""
##查询读取数据
sql1 = "Select * from I_love_taylor"
df_1 = MysqlHelper('XXX', 'Taylor', '123', 3306, 'test').all(sql1)
##插入算好的数据
InsertData(df_Final, 'I_love_taylor')
##删除数据
Delete_From(Table='I_love_taylor')

案例要求1:

截止至2016年6月30日,某企业有10万条固定资产,要求在下一年期间,每个月对其计提折旧,并计算出当月的NBV, 采用直线法。
已知条件:每条资产的(1)固定资产原值;(2)预计可使用寿命;(3)剩余可使用寿命;(4)2016年6月30日NBV。
直线法公式:Dep = Cost/Total Life
预计净残值:假设为0

为了能够更加清晰的展示,这是最简单的一种情况,现实中我们还会遇到加速折旧,会遇到净残值不同的组合,遇到新增/减少固定资产的情况,但是那些只需套用逻辑筛选即可。
传统Pythonic做法
这是最直接,但是也是最不推荐的做法,因为很慢。
将所有数据装进一个dataframe, 然后复制出所要计算的期间,遍历每一条资产的每一个期间,分别计算其折旧和NBV。这样的方法稳定,但是效率过低。
读取的源数据:

读取的元数据

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

后面不用写了,谨记永远不要在dataframe用loc遍历,Loc的本质是location, 相当耗时,运气好,电脑可能不会崩。如果没有特别需要,要把某个指揪出来,不要用Loc。即便要用,我们最好要是通过做字典dic,或者json的方式。
升级版pandas内置函数
首先,每一条资产copy,复制成需要计算的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

    # 创建一个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

    # 创建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]

    # 增加维度!(复制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

        # 增加维度!(复制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....想要以具体时间来判定怎么办?那就需要加一个新字段规定起始日期2016年7月1日,时间戳,定义结尾日期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

那么传统方法,核心的迭代就是以下算法:

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

这是一套比较通用的算法,做一个图比较好理解。

在这里插入图片描述

它其实就是每个资产12期规定为一个模块,规定一个起始期限如7,10000个资产对应10000个模块,每个模块同时迭代,迭代多少以count虚数为定义。当时写完这段,觉得大事可定,轻松了很多。直到跑全量数据后,才意识到这个方法依旧很慢。慢就慢在依旧还要在自己定义的模块里通过虚数遍历。
栈结构stack
下面重点介绍它。stack()会很快,多快呢?线性效率,每增加10倍数据,时间只增加8倍。前两种方法算10000条记录分别是10分钟,1分钟。栈结构只要4秒。
核心思想:将需要计算的都放在列,因此需要上面写的那个class:add_row_col 创建多个列,对列循环运用eval或者apply(lambda公式)。在将每一个结算好的结果,当作一落落得盘子,放在原始数据里。

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 对应的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默认按照索引join。
df_1。reset_index(drop=True,inplace=True)

大致核心就是这样,具体有很多细节,可以自行修改。stack可以做很多,只要是类似beginning-thisamount=ending得滚动迭代,都可以用栈结构。

4. 局限性

最后讲讲局限性。就像之前提到得,它是后进先出,受限得线性结构。那么就会导致,无法个别差异计算。未来将这些落起来得盘子,也只能按顺序摆放。如果是做数据模型得同学,前期一定做好聚类,否则就杯具了。其他用途得,如只是的单纯得财务,人力管理,为了想让它打出231, 132等不同顺序得子弹,我们需要再做一个引用多维度得包。通过唯一主键,对其进行重新排序。另外一个问题,就是它占用空间较大,且对索引得排列要求较高。这也就意味着,一旦你发现数据没有算全,那就从头开始落吧!

And......

如果有何错误,请大家多多指正。如果有其他方法,希望分享,共同进步~!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容