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

1 ↑ 关注 + 星标 ,每天学Python新技能 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 后台回复【大礼包】送你Python自学大礼包 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67来源:凹凸数据

作者简介:

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

大家好,我是老表~

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

大概就是很多跟数据打交道的人都需要面对过很复杂的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%  ))

(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  ))

=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    )  ))

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

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

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)如下:

遍历打印这颗多叉树

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节点合并一下阅读性更好:

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

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    )  ))

合并三次后的内存情况:

4、体验网站

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。

1 推荐阅读 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 突发!彭博社:马云已被限制出境!蚂蚁,不可能上市了 51 52 53 54 一句话,离职了 55 56 57 58 微软官方上线了Python教程,7个章节就把Python说通了! 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

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

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

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

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

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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )