Excel公式太复杂?我花一晚上用Python做了个格式化工具

Stella981
• 阅读 493
                ↑ 关注 + 星标 ,每天学Python新技能 
                
               
              
             
            
           
          
        
   
       
       
       
  
      
      
      
 
     
     
     
 
     
     
     
  
      
      
      
   
       
       
       
    
        
        
         
          
           
            
             
              
               
                
                后台回复【大礼包】送你Python自学大礼包 
                
               
              
             
            
           
          
        
   
       
       
       
  
      
      
      
 
     
     
     

来源:凹凸数据

作者简介:Excel公式太复杂?我花一晚上用Python做了个格式化工具

小小明,熟悉python、java、scala,了解go、c/c++。10年左右编码经验,逻辑思维能力良好,做过windows应用程序开发和大数据开发与运维,会大数据、web全栈开发、自动化办公、pandas数据处理,了解区块链开发、机器学习、 VBA、爬虫。

大家好,我是老表~

之前在 交流群跟一些小伙伴有个讨论:

Excel公式太复杂?我花一晚上用Python做了个格式化工具

大概就是很多跟数据打交道的人都需要面对过很复杂的excel,嵌套层数特别多,肉眼观看很容易蒙圈。有了这样的需求,小小明就有了解决问题的想法,说干就干于是一个比较牛逼的excel公式格式化的工具出现了。

1、效果体验

先看看效果吧:

=IF(C11>100%*C4,IF(C11<=200%*C4,C11*50%-C4*15%,C11*60%-C4*35%),IF(C11<=C4*50%,C11*30%,C11*40%-C4*5%))

的格式化结果是:

=IF(  C11>100%*C4,  IF(    C11<=200%*C4,    C11*50%-C4*15%,    C11*60%-C4*35%  ),  IF(    C11<=C4*50%,    C11*30%,    C11*40%-C4*5%  ))

Excel公式太复杂?我花一晚上用Python做了个格式化工具

(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100-MIN(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,12))/(MAX(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,12)-MIN(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,12))

的格式化结果为:

(  SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)  /  SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)  *  100-MIN(    SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)    /    SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,    12  ))/(  MAX(    SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)    /    SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,    12  )  -  MIN(    SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)    /    SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,    12  ))

Excel公式太复杂?我花一晚上用Python做了个格式化工具

=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),4),1,"")&56),0)))

的格式化结果为:

=IF(  ROW()>COLUMN(),  "",  IF(    ROW()=COLUMN(),    $B15,    ROUNDDOWN(      $B15*INDIRECT(        SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")        &        56      ),      0    )  ))

Excel公式太复杂?我花一晚上用Python做了个格式化工具

如果你已经心动了,可以进入下滑到文末,点击阅读原文!直接使用体验!但本人不保证服务器一直会续费,网址能够永久使用。

不过接下来,将公布这套格式化程序的完整代码和开发思想,有技术能力的小伙伴可以考虑改进该代码。

2、完整代码

__author__ = 'xiaoxiaoming'from collections import dequeimport reclass Node:    def __init__(self, parent=None, tab_size=0):        self.parent = parent        self.tab_size = tab_size        self.data = []    def is_single_node(self):        for e in self.data:            if not isinstance(e, str):                return False        return True    def get_single_text(self):        return "".join(self.data)def split_text_blocks(excel_func_text):    """    将excel公式字符串,按照一定的规则切割成数组    :param excel_func_text: 被切割的excel公式字符串    :return: 切割后的结果    """    excel_func_text = excel_func_text.replace('\n', '').replace('\r', '')    excel_func_text = re.sub(" +", " ", excel_func_text)    lines = []    i, j = 0, 0    while j < len(excel_func_text):        c = excel_func_text[j]        if (c == '(' and excel_func_text[j + 1] != ')') or c == ',':            lines.append(excel_func_text[i:j + 1])            i = j = j + 1        elif c == ')' and excel_func_text[j - 1] != '(':            if i < j:                lines.append(excel_func_text[i:j])                i = j  # 起始文件块置于)处            # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果            k = excel_func_text.find(",", j + 1)            l = excel_func_text.find("(", j + 1, k)            m = excel_func_text.find(")", j + 1, k)            if k != -1 and l == -1 and m == -1:                lines.append(excel_func_text[i:k + 1])                i = j = k + 1            elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':                lines.append(")")                lines.append(excel_func_text[j + 1])                i = j = j + 2            else:                lines.append(")")                i = j = j + 1        elif c == '"':            j = excel_func_text.find('"', j + 1) + 1        else:            j += 1    return linesblank_char_count = 2def combine_node(root, text_max_length=60, max_combine_layer=3):    """    合并最内层的只有纯文本子节点的节点为单个文本节点    :param root: 被合并的节点    :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点    :param max_combine_layer: 最大合并层数    :return:    """    for _ in range(max_combine_layer):        no_change = True        stack = deque([root])        while stack:            node = stack.pop()            tmp = {}            for i, e in enumerate(node.data):                if isinstance(e, Node):                    if e.is_single_node():                        single_text = e.get_single_text()                        if len(single_text) < text_max_length:                            tmp[i] = single_text                    else:                        stack.append(e)            for i, e in tmp.items():                node.data[i] = e            if len(tmp) != 0:                no_change = False        if no_change:            breakdef node_next_line(node):    for i, e in enumerate(node.data):        if isinstance(e, str):            if i == 0 or i == len(node.data) - 1:                tab = node.tab_size - 1            else:                tab = node.tab_size            yield f"{' ' * blank_char_count * tab}{e}"        else:            yield from node_next_line(e)            def excel_func_format(excel_func_text, blank_count=2, combine_single_node=True, text_max_length=60,                      max_combine_layer=3):    """    将excel公式格式化成比较容易阅读的格式    :param excel_func_text: 被格式化的excel公式字符串    :param blank_count: 最终显示的格式化字符串的1个tab用几个空格表示    :param combine_single_node: 是否合并纯文本节点,该参数设置为True后面的参数才生效    :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点    :param max_combine_layer: 最大合并层数    :return: 格式化后的字符串    """    global blank_char_count    blank_char_count = blank_count    blocks = split_text_blocks(excel_func_text)    # print("\n".join(blocks))    # print('-----------拆分结果-----------')    tab_size = 0    node = root = Node()    for block in blocks:        if block.endswith("("):            tab_size += 1            child_node = Node(node, tab_size)            node.data.append(child_node)            node = child_node            node.data.append(block)        elif block.startswith(")"):            tab_size -= 1            node.data.append(block)            node = node.parent        else:            node.data.append(block)    if combine_single_node:        combine_node(root, text_max_length, max_combine_layer)    result = [line for line in node_next_line(root)]    return "\n".join(result)

3、处理流程浅析

下面都以如下公式作为示例:

=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),4),1,"")&56),0)))

文本分块切分

def split_text_blocks(excel_func_text):    """    将excel公式字符串,按照一定的规则切割成数组    :param excel_func_text: 被切割的excel公式字符串    :return: 切割后的结果    """    excel_func_text = excel_func_text.replace('\n', '').replace('\r', '')    excel_func_text = re.sub(" +", " ", excel_func_text)    lines = []    i, j = 0, 0    while j < len(excel_func_text):        c = excel_func_text[j]        if (c == '(' and excel_func_text[j + 1] != ')') or c == ',':            lines.append(excel_func_text[i:j + 1])            i = j = j + 1        elif c == ')' and excel_func_text[j - 1] != '(':            if i < j:                lines.append(excel_func_text[i:j])                i = j  # 起始文件块置于)处            # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果            k = excel_func_text.find(",", j + 1)            l = excel_func_text.find("(", j + 1, k)            m = excel_func_text.find(")", j + 1, k)            if k != -1 and l == -1 and m == -1:                lines.append(excel_func_text[i:k + 1])                i = j = k + 1            elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':                lines.append(")")                lines.append(excel_func_text[j + 1])                i = j = j + 2            else:                lines.append(")")                i = j = j + 1        elif c == '"':            j = excel_func_text.find('"', j + 1) + 1        else:            j += 1    return liness = """=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),        4),1,"")&56),0))) """blocks = split_text_blocks(s)for block in blocks:    print(block)

的运行结果为:

=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")&56),0)))

这端代码首先替换掉所有的换行符,将多个空格替换为单个空格,然后将左右括号和逗号作为切分点进行切分。

但存在一些特殊情况,例如ROW()和COLUMN()括号内部没有任何内容,所有这种括号应该作为普通字符处理,另外被""包含的字符串可能包含括号,也应该作为普通字符。

构建多叉树层次结构

设计数据结构:

class Node:    def __init__(self, parent=None, tab_size=0):        self.parent = parent        self.tab_size = tab_size        self.data = []

parent存储父节点的指针,tab_size存储当前节点的层级,data存储当前节点的所有数据。

构建代码:

tab_size = 0node = root = Node()for block in blocks:    if block.endswith("("):        tab_size += 1        child_node = Node(node, tab_size)        node.data.append(child_node)        node = child_node        node.data.append(block)    elif block.startswith(")"):        tab_size -= 1        node.data.append(block)        node = node.parent    else:        node.data.append(block)

构建完毕后,这段数据在内存中的结构(仅展示data)如下:

Excel公式太复杂?我花一晚上用Python做了个格式化工具

遍历打印这颗多叉树

def node_next_line(node):    for i, e in enumerate(node.data):        if isinstance(e, str):            if i == 0 or i == len(node.data) - 1:                tab = node.tab_size - 1            else:                tab = node.tab_size            yield f"{' ' * 2 * tab}{e}"        else:            yield from node_next_line(e)            result = [line for line in node_next_line(root)]print("\n".join(result))

结果:

=IF(  ROW()>COLUMN(),  "",  IF(    ROW()=COLUMN(),    $B15,    ROUNDDOWN(      $B15*INDIRECT(        SUBSTITUTE(          ADDRESS(            1,            3+COLUMN()-ROW(),             4          ),          1,          ""        )        &        56      ),      0    )  ))

合并最内层的节点

显然将最内层的node5节点合并一下阅读性更好:

Excel公式太复杂?我花一晚上用Python做了个格式化工具

首先给数据结构增加判断是否为纯文本节点的方法:

class Node:    def __init__(self, parent=None, tab_size=0):        self.parent = parent        self.tab_size = tab_size        self.data = []    def is_single_node(self):        for e in self.data:            if not isinstance(e, str):                return False        return True    def get_single_text(self):        return "".join(self.data)

下面是合并纯文本节点的实现,max_combine_layer决定了合并的最大次数,如果合并后长度超过text_max_length参数,则不应用这次合并:

from collections import dequedef combine_node(root, text_max_length=60, max_combine_layer=3):    """    合并最内层的只有纯文本子节点的节点为单个文本节点    :param root: 被合并的节点    :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点    :param max_combine_layer: 最大合并层数    :return:    """    for _ in range(max_combine_layer):        no_change = True        stack = deque([root])        while stack:            node = stack.pop()            tmp = {}            for i, e in enumerate(node.data):                if isinstance(e, Node):                    if e.is_single_node():                        single_text = e.get_single_text()                        if len(single_text) < text_max_length:                            tmp[i] = single_text                    else:                        stack.append(e)            for i, e in tmp.items():                node.data[i] = e            if len(tmp) != 0:                no_change = False        if no_change:            break

合并一次:

combine_node(root, 100, 1)result = [line for line in node_next_line(root)]print("\n".join(result))

结果:

=IF(  ROW()>COLUMN(),  "",  IF(    ROW()=COLUMN(),    $B15,    ROUNDDOWN(      $B15*INDIRECT(        SUBSTITUTE(          ADDRESS(1,3+COLUMN()-ROW(), 4),          1,          ""        )        &        56      ),      0    )  ))

合并二次:

combine_node(root, 100, 2)result = [line for line in node_next_line(root)]print("\n".join(result))

结果:

=IF(  ROW()>COLUMN(),  "",  IF(    ROW()=COLUMN(),    $B15,    ROUNDDOWN(      $B15*INDIRECT(        SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")        &        56      ),      0    )  ))

合并三次:

combine_node(root, 100, 3)result = [line for line in node_next_line(root)]print("\n".join(result))

结果:

=IF(  ROW()>COLUMN(),  "",  IF(    ROW()=COLUMN(),    $B15,    ROUNDDOWN(      $B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")&56),      0    )  ))

合并三次后的内存情况:

Excel公式太复杂?我花一晚上用Python做了个格式化工具

4、体验网站

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。

          推荐阅读
    
         
         
         
  
       
       
       
 
      
      
      

  
      
      
      
   
       
       
       
    
        
        
        
     
         
         
         
      
          
          
          
       
           
           
            
             
              
               
                
                             
                 
                 
                 
              
                  
                  
                  突发!彭博社:马云已被限制出境!蚂蚁,不可能上市了
              
                  
                  
                  一句话,离职了
              
                  
                  
                  微软官方上线了Python教程,7个章节就把Python说通了!
              
                  
                  
                  
             
                 
                 
                  
                
               
              
             
           
      
          
          
          
     
         
         
         
    
        
        
        
   
       
       
       
  
      
      
      

=====================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================

=============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================

       扫码回复「大礼包」后获取大礼
 
      
      
      扫码加我微信备注「三剑客」送你上图三本电子书

==================================================================================================================================================================

本文分享自微信公众号 - Python绿色通道(Python_channel)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这