飞道的博客

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

286人阅读  评论(0)


作者:小小明

作者简介:

小小明,熟悉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%))

的格式化结果是:


   
  1. =IF(
  2.   C11> 100%*C4,
  3.   IF(
  4.     C11<= 200%*C4,
  5.     C11* 50%-C4* 15%,
  6.     C11* 60%-C4* 35%
  7.   ),
  8.   IF(
  9.     C11<=C4* 50%,
  10.     C11* 30%,
  11.     C11* 40%-C4* 5%
  12.   )
  13. )

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

的格式化结果为:


   
  1. (
  2.   SMA(MAX(CLOSE-DELAY(CLOSE, 1), 0), 12, 1)
  3.   /
  4.   SMA(ABS(CLOSE-DELAY(CLOSE, 1)), 12, 1)
  5.   *
  6.    100-MIN(
  7.     SMA(MAX(CLOSE-DELAY(CLOSE, 1), 0), 12, 1)
  8.     /
  9.     SMA(ABS(CLOSE-DELAY(CLOSE, 1)), 12, 1)* 100,
  10.      12
  11.   )
  12. )
  13. /
  14. (
  15.   MAX(
  16.     SMA(MAX(CLOSE-DELAY(CLOSE, 1), 0), 12, 1)
  17.     /
  18.     SMA(ABS(CLOSE-DELAY(CLOSE, 1)), 12, 1)* 100,
  19.      12
  20.   )
  21.   -
  22.   MIN(
  23.     SMA(MAX(CLOSE-DELAY(CLOSE, 1), 0), 12, 1)
  24.     /
  25.     SMA(ABS(CLOSE-DELAY(CLOSE, 1)), 12, 1)* 100,
  26.      12
  27.   )
  28. )

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

的格式化结果为:


   
  1. =IF(
  2.   ROW()>COLUMN(),
  3.    "",
  4.   IF(
  5.     ROW()=COLUMN(),
  6.     $B15,
  7.     ROUNDDOWN(
  8.       $B15*INDIRECT(
  9.         SUBSTITUTE(ADDRESS( 1, 3+COLUMN()-ROW(),  4), 1, "")
  10.         &
  11.          56
  12.       ),
  13.        0
  14.     )
  15.   )
  16. )

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

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

完整代码


   
  1. __author__ =  'xiaoxiaoming'
  2. from collections  import deque
  3. import re
  4. class Node:
  5.     def __init__(self, parent=None, tab_size= 0):
  6.         self.parent = parent
  7.         self.tab_size = tab_size
  8.         self.data = []
  9.     def is_single_node(self):
  10.          for e in self.data:
  11.              if not isinstance(e, str):
  12.                  return False
  13.          return True
  14.     def get_single_text(self):
  15.          return  "".join(self.data)
  16. def split_text_blocks(excel_func_text):
  17.      "" "
  18.     将excel公式字符串,按照一定的规则切割成数组
  19.     :param excel_func_text: 被切割的excel公式字符串
  20.     :return: 切割后的结果
  21.     " ""
  22.     excel_func_text = excel_func_text.replace( '\n''').replace( '\r''')
  23.     excel_func_text = re.sub( " +"" ", excel_func_text)
  24.     lines = []
  25.     i, j =  00
  26.     while j <  len(excel_func_text):
  27.         c = excel_func_text[j]
  28.          if (c ==  '(' and excel_func_text[j +  1] !=  ')') or c ==  ',':
  29.             lines. append(excel_func_text[i:j +  1])
  30.             i = j = j +  1
  31.         elif c ==  ')' and excel_func_text[j -  1] !=  '(':
  32.              if i < j:
  33.                 lines. append(excel_func_text[i:j])
  34.                 i = j  # 起始文件块置于)处
  35.             # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果
  36.             k = excel_func_text.find( ",", j +  1)
  37.             l = excel_func_text.find( "(", j +  1, k)
  38.             m = excel_func_text.find( ")", j +  1, k)
  39.              if k !=  -1 and l ==  -1 and m ==  -1:
  40.                 lines. append(excel_func_text[i:k +  1])
  41.                 i = j = k +  1
  42.             elif j +  1 <  len(excel_func_text) and excel_func_text[j +  1] !=  ')':
  43.                 lines. append( ")")
  44.                 lines. append(excel_func_text[j +  1])
  45.                 i = j = j +  2
  46.              else:
  47.                 lines. append( ")")
  48.                 i = j = j +  1
  49.         elif c ==  '"':
  50.             j = excel_func_text.find( '"', j +  1) +  1
  51.          else:
  52.             j +=  1
  53.      return lines
  54. blank_char_count =  2
  55. def combine_node(root, text_max_length= 60, max_combine_layer= 3):
  56.      "" "
  57.     合并最内层的只有纯文本子节点的节点为单个文本节点
  58.     :param root: 被合并的节点
  59.     :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  60.     :param max_combine_layer: 最大合并层数
  61.     :return:
  62.     " ""
  63.      for _ in  range(max_combine_layer):
  64.         no_change = True
  65.         stack = deque([root])
  66.         while stack:
  67.             node = stack.pop()
  68.             tmp = {}
  69.              for i, e in enumerate(node.data):
  70.                  if isinstance(e, Node):
  71.                      if e.is_single_node():
  72.                         single_text = e.get_single_text()
  73.                          if  len(single_text) < text_max_length:
  74.                             tmp[i] = single_text
  75.                      else:
  76.                         stack. append(e)
  77.              for i, e in tmp.items():
  78.                 node.data[i] = e
  79.              if  len(tmp) !=  0:
  80.                 no_change = False
  81.          if no_change:
  82.              break
  83. def node_next_line(node):
  84.      for i, e in enumerate(node.data):
  85.          if isinstance(e, str):
  86.              if i ==  0 or i ==  len(node.data) -  1:
  87.                 tab = node.tab_size -  1
  88.              else:
  89.                 tab = node.tab_size
  90.             yield f "{' ' * blank_char_count * tab}{e}"
  91.          else:
  92.             yield from node_next_line(e)
  93.             
  94. def excel_func_format(excel_func_text, blank_count= 2, combine_single_node=True, text_max_length= 60,
  95.                       max_combine_layer= 3):
  96.      "" "
  97.     将excel公式格式化成比较容易阅读的格式
  98.     :param excel_func_text: 被格式化的excel公式字符串
  99.     :param blank_count: 最终显示的格式化字符串的1个tab用几个空格表示
  100.     :param combine_single_node: 是否合并纯文本节点,该参数设置为True后面的参数才生效
  101.     :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  102.     :param max_combine_layer: 最大合并层数
  103.     :return: 格式化后的字符串
  104.     " ""
  105.     global blank_char_count
  106.     blank_char_count = blank_count
  107.     blocks = split_text_blocks(excel_func_text)
  108.     #  print( "\n".join(blocks))
  109.     #  print( '-----------拆分结果-----------')
  110.     tab_size =  0
  111.     node = root = Node()
  112.      for block in blocks:
  113.          if block.endswith( "("):
  114.             tab_size +=  1
  115.             child_node = Node(node, tab_size)
  116.             node.data. append(child_node)
  117.             node = child_node
  118.             node.data. append(block)
  119.         elif block.startswith( ")"):
  120.             tab_size -=  1
  121.             node.data. append(block)
  122.             node = node.parent
  123.          else:
  124.             node.data. append(block)
  125.      if combine_single_node:
  126.         combine_node(root, text_max_length, max_combine_layer)
  127.     result = [line  for line in node_next_line(root)]
  128.      return  "\n".join(result)

处理流程浅析

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


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

文本分块切分


   
  1. def split_text_blocks(excel_func_text):
  2.      "" "
  3.     将excel公式字符串,按照一定的规则切割成数组
  4.     :param excel_func_text: 被切割的excel公式字符串
  5.     :return: 切割后的结果
  6.     " ""
  7.     excel_func_text = excel_func_text.replace( '\n''').replace( '\r''')
  8.     excel_func_text = re.sub( " +"" ", excel_func_text)
  9.     lines = []
  10.     i, j =  00
  11.     while j <  len(excel_func_text):
  12.         c = excel_func_text[j]
  13.          if (c ==  '(' and excel_func_text[j +  1] !=  ')') or c ==  ',':
  14.             lines. append(excel_func_text[i:j +  1])
  15.             i = j = j +  1
  16.         elif c ==  ')' and excel_func_text[j -  1] !=  '(':
  17.              if i < j:
  18.                 lines. append(excel_func_text[i:j])
  19.                 i = j  # 起始文件块置于)处
  20.             # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果
  21.             k = excel_func_text.find( ",", j +  1)
  22.             l = excel_func_text.find( "(", j +  1, k)
  23.             m = excel_func_text.find( ")", j +  1, k)
  24.              if k !=  -1 and l ==  -1 and m ==  -1:
  25.                 lines. append(excel_func_text[i:k +  1])
  26.                 i = j = k +  1
  27.             elif j +  1 <  len(excel_func_text) and excel_func_text[j +  1] !=  ')':
  28.                 lines. append( ")")
  29.                 lines. append(excel_func_text[j +  1])
  30.                 i = j = j +  2
  31.              else:
  32.                 lines. append( ")")
  33.                 i = j = j +  1
  34.         elif c ==  '"':
  35.             j = excel_func_text.find( '"', j +  1) +  1
  36.          else:
  37.             j +=  1
  38.      return lines
  39. s =  "" "=IF(ROW()>COLUMN()," ",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),
  40.         4),1," ")&56),0))) " ""
  41. blocks = split_text_blocks(s)
  42. for block in blocks:
  43.      print(block)

的运行结果为:


   
  1. =IF(
  2. ROW()>COLUMN(),
  3. "",
  4. IF(
  5. ROW()=COLUMN(),
  6. $B15,
  7. ROUNDDOWN(
  8. $B15*INDIRECT(
  9. SUBSTITUTE(
  10. ADDRESS(
  11. 1,
  12. 3+COLUMN()-ROW(),
  13.   4
  14. ),
  15. 1,
  16. ""
  17. )
  18. &
  19. 56
  20. ),
  21. 0
  22. )
  23. )
  24. )

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

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

构建多叉树层次结构

设计数据结构:


   
  1. class Node:
  2.     def __init__(self, parent=None, tab_size= 0):
  3.         self.parent = parent
  4.         self.tab_size = tab_size
  5.         self.data = []

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

构建代码:


   
  1. tab_size =  0
  2. node = root = Node()
  3. for block in blocks:
  4.      if block.endswith( "("):
  5.         tab_size +=  1
  6.         child_node = Node(node, tab_size)
  7.         node.data. append(child_node)
  8.         node = child_node
  9.         node.data. append(block)
  10.     elif block.startswith( ")"):
  11.         tab_size -=  1
  12.         node.data. append(block)
  13.         node = node.parent
  14.      else:
  15.         node.data. append(block)

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

遍历打印这颗多叉树


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

结果:


   
  1. =IF(
  2.   ROW()>COLUMN(),
  3.    "",
  4.   IF(
  5.     ROW()=COLUMN(),
  6.     $B15,
  7.     ROUNDDOWN(
  8.       $B15*INDIRECT(
  9.         SUBSTITUTE(
  10.           ADDRESS(
  11.              1,
  12.              3+COLUMN()-ROW(),
  13.               4
  14.           ),
  15.            1,
  16.            ""
  17.         )
  18.         &
  19.          56
  20.       ),
  21.        0
  22.     )
  23.   )
  24. )

合并最内层的节点

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

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


   
  1. class Node:
  2.     def __init__(self, parent=None, tab_size= 0):
  3.         self.parent = parent
  4.         self.tab_size = tab_size
  5.         self.data = []
  6.     def is_single_node(self):
  7.          for e in self.data:
  8.              if not isinstance(e, str):
  9.                  return False
  10.          return True
  11.     def get_single_text(self):
  12.          return  "".join(self.data)

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


   
  1. from collections  import deque
  2. def combine_node(root, text_max_length= 60, max_combine_layer= 3):
  3.      "" "
  4.     合并最内层的只有纯文本子节点的节点为单个文本节点
  5.     :param root: 被合并的节点
  6.     :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  7.     :param max_combine_layer: 最大合并层数
  8.     :return:
  9.     " ""
  10.      for _ in  range(max_combine_layer):
  11.         no_change = True
  12.         stack = deque([root])
  13.         while stack:
  14.             node = stack.pop()
  15.             tmp = {}
  16.              for i, e in enumerate(node.data):
  17.                  if isinstance(e, Node):
  18.                      if e.is_single_node():
  19.                         single_text = e.get_single_text()
  20.                          if  len(single_text) < text_max_length:
  21.                             tmp[i] = single_text
  22.                      else:
  23.                         stack. append(e)
  24.              for i, e in tmp.items():
  25.                 node.data[i] = e
  26.              if  len(tmp) !=  0:
  27.                 no_change = False
  28.          if no_change:
  29.              break

合并一次:


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

结果:


   
  1. =IF(
  2.   ROW()>COLUMN(),
  3.    "",
  4.   IF(
  5.     ROW()=COLUMN(),
  6.     $B15,
  7.     ROUNDDOWN(
  8.       $B15*INDIRECT(
  9.         SUBSTITUTE(
  10.           ADDRESS( 1, 3+COLUMN()-ROW(),  4),
  11.            1,
  12.            ""
  13.         )
  14.         &
  15.          56
  16.       ),
  17.        0
  18.     )
  19.   )
  20. )

合并二次:


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

结果:


   
  1. =IF(
  2.   ROW()>COLUMN(),
  3.    "",
  4.   IF(
  5.     ROW()=COLUMN(),
  6.     $B15,
  7.     ROUNDDOWN(
  8.       $B15*INDIRECT(
  9.         SUBSTITUTE(ADDRESS( 1, 3+COLUMN()-ROW(),  4), 1, "")
  10.         &
  11.          56
  12.       ),
  13.        0
  14.     )
  15.   )
  16. )

合并三次:


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

结果:


   
  1. =IF(
  2.   ROW()>COLUMN(),
  3.    "",
  4.   IF(
  5.     ROW()=COLUMN(),
  6.     $B15,
  7.     ROUNDDOWN(
  8.       $B15*INDIRECT(SUBSTITUTE(ADDRESS( 1, 3+COLUMN()-ROW(),  4), 1, "")& 56),
  9.        0
  10.     )
  11.   )
  12. )

合并三次后的内存情况:

体验网址

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。


转载:https://blog.csdn.net/zhuxiao5/article/details/111659352
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场