作者:小小明

作者简介:

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

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

(凹凸数据公众号后台回复“进群”即可加群)

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

效果体验

先看看效果吧:

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

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

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

完整代码

__author__ = 'xiaoxiaoming'from collections import deque
import reclass Node:def __init__(self, parent=None, tab_size=0):self.parent = parentself.tab_size = tab_sizeself.data = []def is_single_node(self):for e in self.data:if not isinstance(e, str):return Falsereturn Truedef 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, 0while 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 + 1elif 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 + 1elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':lines.append(")")lines.append(excel_func_text[j + 1])i = j = j + 2else:lines.append(")")i = j = j + 1elif c == '"':j = excel_func_text.find('"', j + 1) + 1else:j += 1return 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 = Truestack = 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_textelse:stack.append(e)for i, e in tmp.items():node.data[i] = eif len(tmp) != 0:no_change = Falseif 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 - 1else:tab = node.tab_sizeyield 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_countblank_char_count = blank_countblocks = split_text_blocks(excel_func_text)# print("\n".join(blocks))# print('-----------拆分结果-----------')tab_size = 0node = root = Node()for block in blocks:if block.endswith("("):tab_size += 1child_node = Node(node, tab_size)node.data.append(child_node)node = child_nodenode.data.append(block)elif block.startswith(")"):tab_size -= 1node.data.append(block)node = node.parentelse: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)

处理流程浅析

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

=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, 0while 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 + 1elif 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 + 1elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':lines.append(")")lines.append(excel_func_text[j + 1])i = j = j + 2else:lines.append(")")i = j = j + 1elif c == '"':j = excel_func_text.find('"', j + 1) + 1else:j += 1return 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 = parentself.tab_size = tab_sizeself.data = []

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

构建代码:

tab_size = 0
node = root = Node()
for block in blocks:if block.endswith("("):tab_size += 1child_node = Node(node, tab_size)node.data.append(child_node)node = child_nodenode.data.append(block)elif block.startswith(")"):tab_size -= 1node.data.append(block)node = node.parentelse: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 - 1else:tab = node.tab_sizeyield 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 = parentself.tab_size = tab_sizeself.data = []def is_single_node(self):for e in self.data:if not isinstance(e, str):return Falsereturn Truedef 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 = Truestack = 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_textelse:stack.append(e)for i, e in tmp.items():node.data[i] = eif len(tmp) != 0:no_change = Falseif 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))
)

合并三次后的内存情况:

体验网址

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。

Excel公式太复杂?我花一晚上做了个格式化工具相关推荐

  1. 用ChatGPT生成Excel公式,太方便了

    ChatGPT 自去年 11 月 30 日 OpenAI 重磅推出以来,这款 AI 聊天机器人迅速成为 AI 界的「当红炸子鸡」.一经发布,不少网友更是痴迷到通宵熬夜和它对话聊天,就为了探究 Chat ...

  2. 用ChatGPT生成Excel公式,太方便了!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

  3. 空间连接时计算总和_瞬间搞定一月数据汇总!这个Excel求和公式太牛了

    原标题:瞬间搞定一月数据汇总!这个Excel求和公式太牛了 之前推过一期跨表公式合集,其中有一个是利用sum进行多表求和 [例]如下图所示,需要在汇总表中统计1~30日的各个商品销量合计(日报表和汇总 ...

  4. python处理表格很厉害么_还在用excel?太落伍了,现在都用Python和Jupyter处理数据了...

    全文共3246字,预计学习时长8分钟 如果你从事的是商业交易或者在金融机构工作,Excel是必备技能. 你可以用它分析价格及其他tick数据.评估投资组合.计算风险价值.进行事后检等等.你熟练掌握数据 ...

  5. Excel公式与函数——每天学一个

    1. 根据刘伟的视频讲解进行总结,网上讲Excel公式与函数的貌似就他讲的还不错.在他的微博里看到现在的照片胖了不少,不过还挺帅的,不再是以前那个小屌丝了. 2. 一共53个视频,去掉一个开头,去掉一 ...

  6. 用 ChatGPT 将 Excel 工作效率提高 10 倍!离做PPT还远吗

    源|机器之心 在未来,精通 Excel 或许不再是简历亮点了. ChatGPT 自去年 11 月 30 日 OpenAI 重磅推出以来,这款 AI 聊天机器人迅速成为 AI 界的「当红炸子鸡」.一经发 ...

  7. oracle 统计一年中每个月数据总和_excel表格有每月数据 怎样统计全年的-用excel公式怎样计算每年每个月的数据总和?...

    excel表格中如何统计1月到12月的数据 我用的是Excel07版为你的表已经是存在的,并能力有限,所只能给你说下面这一种方法了,用着也单的!下边我做一个示范: 第一步 在Excel表格右侧空白任意 ...

  8. matlab用辛普森公式求积分_如何用Excel公式求最大值对应的行列序号

    微信公众号: Excel and Python 微信名搜索: 实用办公编程技能 如何用Excel公式求最大值对应的行列序号呢? 下面,我们来看看来自问题互动栏目的一个具体问题. 具体问题:求出哪一天哪 ...

  9. 将Vba代码转换成Php代码,将这个Excel公式转换成VBA代码(函数)

    将这个Excel公式转换成VBA代码(函数) 这是Excel中的公式,我试图转换为VBA代码: =IFERROR(IF(effDate>curDate,0,IF((curDate-effDate ...

最新文章

  1. js如何动态的加载js文件
  2. ansible部署tomcat及 include机制
  3. Python基础第19天
  4. C# cs文件表头模版
  5. C#上位机串口软件开发--第1讲 工程创建
  6. PPT 下载 | 神策数据张涛:企业服务客户全生命周期运营三步曲客情诊断 解决方案库...
  7. 程序员成长最快的环境
  8. java请求servlet,[Java]Servlet发送Post请求
  9. java开发分支_如何选择Java 的分支?
  10. 容器编排技术 -- Kubernetes Master-Node通信
  11. 【英语学习】【Level 07】U01 Making friends L3 Do you eat here a lot?
  12. 华为云空间联系人是不是机主_华为Mate 30系列国内发布 超强隐私安全保护放大招...
  13. python画圣诞树代码解读_实战 | 教你用Python画各种版本的圣诞树
  14. poj 3080 Blue Jeans kmp+枚举
  15. Linux学习笔记6 - 用户和组群账户管理
  16. unityar自动识别人脸_unity人脸识别源码Dlib FaceLandmark Detector
  17. python中的if语句
  18. 美国研究生 计算机专业 保底学校,2019Fall录取留学生最多的11所美国大学,有你的保底院校吗?...
  19. 我以前的痛苦,你也拥有吗?
  20. 2022 年最佳 15 款监控工具!你不可错过

热门文章

  1. 安东尼罗宾--激发你的无限潜能[连载]--9 10章
  2. excel用vlookup函数得到的值都是#N/A
  3. 大家看看 没有事情可以赚点小钱
  4. 《构建之法》第八章 需求分析
  5. EOS合约开发第十九章-集中博彩游戏合约设计
  6. 记录-mysql操作-crontab操作
  7. html分页插件大全,前端jquery分页插件推荐
  8. java快速排序for_快速排序(java实现)
  9. 2020-07-12
  10. 8款优秀国产办公软件