之前跟一些小伙伴有个讨论:

image-20201214091536248

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

image-20201214165452189

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

image-20201214165926821

(文末有体验网址)

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

完整代码

__author__ = 'xiaoxiaoming'

from collections import deque
import re


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)


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 lines blank_char_count = 2 def 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 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"{' ' * 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)

处理流程浅析

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

=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 lines s = """=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 = 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)

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

image-20201214180114188

遍历打印这颗多叉树

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

image-20201214181546248

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

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 deque

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

合并三次后的内存情况:

image-20201214182511540

体验网址

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。

到此这篇关于python实现excel公式格式化的示例代码的文章就介绍到这了,更多相关python excel公式格式化内容请搜索乐虎体育以前的文章或继续浏览下面的相关文章希望大家以后多多支持乐虎体育!

标签:

python实现excel公式格式化的示例代码的更多相关文章

  1. python常用模块的常用方法介绍 os、math、random、time、datetime、国内常见镜像

    导入模块的一些语法from random import randint#2、from 模块名 import 函数名,导入模块里的一个方法或变量 from math import * #3、from 模块名 import * ,导入模块里的'所有'(并不是所有的都能导进来)方法和变量 import d......

  2. 史上最详细的Python打包成exe文件教程

    打包成exe文件可以让python代码在没有python环境的条件下,依然能够运行,实在是码农们写追女朋友表白、情人节浪漫的必需品!1、使用豆瓣镜像源下载: pyinstaller有需要了解如何使用国内镜像的小伙伴可以滴滴到此:国内镜像源详细使用教程!https://blog.csdn.net/xt......

  3. pandas groupby分组对象的组内排序解决方案

    问题:根据数据某列进行分组,选择其中另一列大小top-K的的所在行数据解析:求解思路很清晰,即先用groupby对数据进行分组,然后再根据分组后的某一列进行排序,选择排序结果后的top-K结果案例:取一下dataframe中B列各对象中C值最高所在的行df = pd.DataFrame({"......

  4. python日志通过不同的等级打印不同的颜色(示例代码)

    1,不用第三方库# coding: utf-8import loggingBLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)RESET_SEQ = "\033[0m"COLOR_SEQ = "......

  5. python 图像增强算法实现详解

    使用python编写了共六种图像增强算法:1)基于直方图均衡化2)基于拉普拉斯算子3)基于对数变换4)基于伽马变换5)限制对比度自适应直方图均衡化:CLAHE6)retinex-SSR7)retinex-MSR其中,6和7属于同一种下的变化。将每种方法编写成一个函数,封装,可以直接在主函数中调用。采......

  6. python 使用OpenCV进行简单的人像分割与合成

    实现思路通过背景建模的方法,对源图像中的动态人物前景进行分割,再将目标图像作为背景,进行合成操作,获得一个可用的合成影像。实现步骤如下。使用BackgroundSubtractorMOG2进行背景分割BackgroundSubtractorMOG2是一个以高斯混合模型为基础的背景前景分割算法,混合高......

  7. Python爬虫-抓取手机APP数据

    抓取超级课程表话题数据。博文:http://my.oschina.net/jhao104/blog/606922#!/usr/local/bin/python2.7# -*- coding: utf8 -*-"""超级课程表话题抓取"""i......

  8. python中re模块知识点总结

    一、什么是正则表达式?正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。二、正则表达式的匹配规则1.表......

  9. 超详细PyTorch实现手写数字识别器的示例代码

    前言深度学习中有很多玩具数据,mnist就是其中一个,一个人能否入门深度学习往往就是以能否玩转mnist数据来判断的,在前面很多基础介绍后我们就可以来实现一个简单的手写数字识别的网络了数据的处理我们使用pytorch自带的包进行数据的预处理import torchimport torchvision......

  10. Python学习(1) (python特点、优缺点)

    Python学习(1)一、python的特点二、python的优缺点1.优点2.缺点三、python源程序的基本概念一、python的特点1. python 是完全面向对象的语言 函数、模块、数字、字符串都是对象,在python中一切皆为对象完全支持继承、重载、多重继承支持重载运算符,也支持泛型设计......

随机推荐

  1. python生成二维码

    python生成二维码需要用到的包pip install qrcode 代码:import qrcode from PIL import Image # 如果需要在二维码中添加图片logo需要 # 模块导入 data = "www.baidu.com" img_name = '二......

  2. Java中,那些关于String和字符串常量池你不得不知道的东西

    老套的笔试题在一些老套的笔试题中,会要你判断s1==s2为false还是true,s1.equals(s2)为false还是true。String s1 = new String("xyz");String s2 = "xyz";System.out.prin......

  3. php swoft框架实例用法

    在框架选择上,不少人会觉得swoft才是最强PHP框架,尤其是在常驻内存模式的应用级高性能框架,性能是这一堆页面级框架遥不可及的,但是使用上稍微有点欠缺,内容稍微复杂,不如easyswoole好上手,但是性能上是绝对的好用,大家如果在编写项目时候,强烈要求使用性能,就可以了解下这个框架。使用场景微服......

  4. Python爬虫爬取全球疫情数据并存储到mysql数据库的步骤

    思路:使用Python爬虫对腾讯疫情网站世界疫情数据进行爬取,封装成一个函数返回一个 字典数据格式的对象,写另一个方法调用该函数接收返回值,和数据库取得连接后把 数据存储到mysql数据库。一、mysql数据库建表CREATE TABLE world(id INT(11) N......

  5. ASP.NET Core错误处理中间件[2]: 开发者异常页面

    异常页面的DeveloperExceptionPageMiddleware中间件,该中间件在捕捉到后续处理过程中抛出的异常之后会返回一个媒体类型为text/html的响应,后者在浏览器上会呈现一个错误页面。由于这是一个为开发者提供诊断信息的异常页面,所以可以将其称为开发者异常页面(Developer......

  6. Android 基于agora 开发视频会议的代码

    一、概述参照官方demo,基于agora开发,输入会议号(频道)和显示名称 参会,可设置参会选项。支持用户注册和登录。支持多人参会。二、效果三、代码package io.agora.openvcall.ui;import android.content.Intent;import android.o......

  7. Linux中自定义shell脚本启动jar包的方法

    一键启动、停止、重启 java项目创建.sh文件vi XXX.sh编写shell脚本#!/bin/shport=8080 #定义变量等号左右不能有空格jar_name=/opt/oaclou/XXX.jar#运行脚本提示信息tips(){echo "--------------------......

  8. Java中的权限修饰符(protected)示例详解

    前言大部分来自:https://blog.csdn.net/justloveyou_/article/details/61672133。并在这个博客的基础上,加上了自己的一些理解。权限控制表修饰词本类同一个包的类继承类其他类private√×××无(默认)√√×......

  9. JavaScript中的事件委托机制跟深浅拷贝

    今天聊下JavaScript中的事件委托跟深浅拷贝一、事件委托首先呢,介绍一下事件绑定//方法一:通过onclick 点击 function clickEvent(){alert("点击事件");}//方法二:通过addEventListener 点击 var btn = doc......

  10. java中Map、Set、List的简单使用教程(快速入门)

    Map、Set、ListList的常用方法1、创建List list = new ArrayList<>(); List list = new LinkedList<>(); //同时可以作为链表用List> list = new ArrayList<>()......