数学公式编辑器的探索与实现

  1. 研究目的

随着个人计算机的普及,个人编辑的文档数目增多,常常会遇到数学公式输入不便的困难,对于一本理科教材,书中的公式十分繁琐,微软提供的所见即所得的输入方式显然不能满足教材编写者的需要,这时LaTeX提供了完整的解决方案,利用LaTeX提供的命令,可以简单的输出所想的数学公式,但对于LaTeX不易上手,命令繁多,功能也许对于某些用户来说过于累赘,本文探讨了一种类LaTeX公式输出的方法,来解决以上问题。

  1. 实现方法

在探讨这个问题之前,我们翻阅了相关资料,研究了其他人在这方面上的研究。

番禺技术职业学院的曾青松和中山大学的伍小明在《数学公式编辑器的实现技术研究》中提出了一种有向树形结构。这种数据结构不能将公式的整体放入数据结构中,只能将单个简单的非线性结构放入,对于子结构中既含有线性结构又含有非线性结构却得不到很好的支持,而且单向树不利于返回父节点,故改进为双向树也许能够更好的支持公式的存储。这种结构的好处是结构简单,可以简单的放入XML语言中存储。

中山大学的曾青松的硕士论文《基于MathML的科技文献公式编辑器的设计与实现》中详细的解释并改进了公式树的数据结构。这个树中揉入了兄弟树的概念,左子树为子节点,这里说的是非线性结构的子结构,右子树为该节点同层次的后节点,这样整个树形结构便可以存下整个公式,而不再是单个非线性结构,但这样由于左右子树不一致,且左右子树的类型个数未知,不方便遍历, 例如,分式的左子树中首先含有分母,分母的右子树是分子,先遍历分母,再遍历分子,再返回分式,这时不方便返回,现在分子的父结构指针指向的是分母,而其实应该实际指向分式,这样看来这种数据结构很好的存储了整个公式,但却不利于遍历。

在西南交通大学的李先岭的硕士论文《基于识别的公式编辑器的研究和开发》中提出了一种双链表结构,先叙述了如何处理线性结构,然后将非线性结构比喻成一个数据结,节点内部按照同样处理,本文叙述的数据结构和其类似,又借鉴了公式树的概念,比其更形象,更易理解,遍历等算法也十分容易简便。

  1. 数学公式的描述和表示方法

数学公式编辑器的复杂之处是它不能用一个线性结构来表示,但其都是由固定的组成部分的,例如分式有分子、分母,跟式有开方次数和被开方内容,上下标有主体、下标、上标等。

总体来说,数学符号分两种:

  1. 水平排列方式

水平排列方式是指结构的x坐标变化,y坐标不变的排列方式。最常用的是线性结构,多数数学符号也属于这一部分,像希腊字母、大于号、小于号等也属于水平排列方式。

  1. 异型排列方式

异型排列方式是指结构的x坐标和y坐标都变化的排列方式。相类似分数、指数、跟式、矩阵等都属于这一类型,这一类型在计算结构位置时比较难处理,是整个数据处理关注的重点。

异型排列方式其实还可以通过以下分类方式细化成为几个大类,简化了部分公式的处理。

  1. 不同的异型排列方式

      1. 子参数的个数不同,子参数的位置不同

这种类别比较多,对于处理并没有特别的意义,像类似分数与上下标、矩阵等。

      1. 子参数的个数相同,子参数的位置不同

这种因为仅仅位置不同,所以可以互相参考数据结构和处理方式,但真正计算符号位置时就没有什么共同之处了,像例如跟式与分数,都含有两个参数,只不过参数放的位置不同,完全可以利用同一套数据结构,将计算位置的函数分别重载即可。

  1. 相同的异型排列方式

这种其实并不少见,像类似大于号、小于号,所有的希腊字母,积分与重积分,只是计算符号输出的不同,他们的参数个数,参数位置全部一样,完全可以套用一套数据结构与算法,只用替换输出的符号即可。

  1. 数据结构的设计与分析

纯树形的数据不利于存储公式中多个结构,单纯的链式结构又不利于存储非线性结构,链形和树形并行的结构可以很好的解决这些问题,这个数据结构主要是以链式将整个公式穿起来,遇到非线性结构便展开形成树形结构将非线型结构存储起来,这个数据结构就像一个高速公路网,有一条主干道,从北京出发,中途会路过一些省份,但并不是主要目的地,路过省份后会形成一些岔路,这些岔路可能是该省的主干道,该省的主干道又会形成一些岔路通往各个县。如下图,整个公式是一条主干道,到分数后,形成分子、分母两条岔路,分子又是一条通往各县的岔路,分子到了指数有形成了到上标村的岔路,但主干道并没有结束,继续通往分数,我们看到原国家的主干道并没有到分数这里结束,继续通往了目的地g,这个就是本文数据结构的大概含义。

下面具体阐述一下这种数据结构,由于所有数学符号都有一些共有的元素,所以创建CBase基类,囊括所有共有元素。这里起点坐标与终点坐标指的是该结构的其结构的起点与终点,终点坐标主要是帮助下一个结构计算起点的;父结构指针指的是前结构和父结构,主要是看该节点处于链表树的什么位置,分叉处指的是父结构,在干道上指的是前结构;线性量是用来存储字符的,类似希腊字母,大于号、普通线性结构都只需要一个存储线性量的存储,像分式、跟式这种递归到最后仍然是线性量的存储;类型是指该节点是什么类型,是分数还是分式;深度是指处于哪个级别,县级还是省级。

 

基类设计好了,下面准备设计后续的数学符号类,剩下的只需要设计出每个数学符号特殊的地方就可以了,例如分数类只需要再添加分子和分母的结构指针、分子分母的大小,这里的大小指的是长度,在分子分母绘制后要比较大小,小的要居中,分数线的长度要对其大的长度,所以大小是很有必要的。有一些相似的类,我们可以再采取利用继承代码复用,例如上下标和积分类就非常相似,只是积分号要大一些,上下标的上标和下标要近一点而已,我们可以采取先设计上下标类,再让积分类继承,这样节省了一部分代码,类似的类还有很多。

最后说明一下,我们设计了CExpression类用来继承所有的数学符号类,这里主要有两个原因,其一是为了方便链表树的遍历,如果链表树中存在了多种类型的指针,遍历起来就非常麻烦,后节点、前节点都不清楚是什么类型,对于遍历十分麻烦;其二是例如分子节点里面可能还有一个上下标类,也可能是积分类或者是分数类,为了让分子节点能够存下任何一个类,我们必须设计一个类来保证可以存下任一数学符号类,CExpression设计意义就在于此。

  1. 主要算法的探讨

下面探讨一下主要算法,我们先探讨一下整体输出数学公式的算法,最后再讨论解析TeX的语法的方法,因为整体输出使用与任何语法,有普遍性,故而优先讨论。

首先建立头结点,建树,然后最重要的一步工作是验证公式十分解析正确,公式输入的是否合法,这一步不进行会导致后续的代码出错,程序崩溃,下面一步是第一次遍历,第一次遍历是用来计算级别的,计算的级别用来在计算位置函数中确定字体大小,第二次遍历是用来计算公式中最高点和最低点的坐标的,因为起初是以(00)为起点,所以可能会导致一部分成为负坐标,我们要将最高点坐标向下移到(00),使不管公式有多大都让公式的最高点在0点,下一步是按照计算结果输出,输出后进行删除节点,这个也可以作为程序接口,进行合理扩展,有关于扩展问题我们会再后面另行讨论。

 

由于该数据结构是从数据结构角度来说就是一个树形结构,只不过是稀疏的数据结构,所以不再详细叙述遍历算法、移动节点和删除节点的算法,下面详细讨论一下有关与该数据结构如何计算位置的算法。

为了更好的理解算法,我们必须想象每个结构中还可以嵌套其他结构的样子,即使结构中放的是线性量,该结构的后节点仍可以存储数据,即使后节点为空,也不代表该公式链表树解析完成,很有可能是省道到头了,国道依然再继续,假象我们是一辆运输车,车上拉了相当多的货物,我们的任务是先到各个低级别的地方去,直到尽头,然后返回高级别的道路,直到国道结束,计算其实也是一种遍历过程,只不过遍历的过程中我们要记录每个地方的一些数据,再根据这些数据,根据地方级别推算出给下一个地方的东西数量。

下面进入正题,我们首先看看每种数学符号的位置特点,我们主要讨论几种特殊常见的数学符号,其他的可以以此类推。

当遍历到分数类时将分子分母的起点标记出来,分子高于基线,分母低于基线,然后开始遍历分子节点,再遍历分母节点,遍历出分子大小和分母大小后,比较大小,分数线终点为大的终点,小的起点挪到中间,终点也向后挪,至此分数的计算已经完了。

  1. 实验与测试

根据上面的理论我们讨论的数据结构算法,编写实验程序,验证数据结构算法的可行性。

程序界面如下:

 

程序下方为输入框,输入符合TeX语法的命令,上面为输出框,为了方便,程序的输出参数外置,可以随时调整。

下面输入命令测试:

 

实验证明我们的数据结构足以存储中等复杂的数学公式,由于算法问题,高度复杂的公式暂时无法实现。

  1. 总结

  1. 设计不足

这个程序的算法有一些不足,就是分子分母、上下标离基线距离固定,但实际测试过程中,如果分子分母、上下标嵌套过多,会导致过大,上下标重叠,先想到改进算法应该将上的最低点和下的最高点的位置与基线位置固定,这样不论上标与下标多大,也可以保证不重叠,保证公式的美观。

  1. 未来扩展

未来可以利用程序接口扩展,扩展到其他平台,开发网络程序,或者根据接口自动替换文本程序中符合TeX数学公式语法的命令。

这次对于数学公式编辑器的设计,主要写了数据结构部分的设计思想,这一部分十分重要,比算法还要重要,好的数据结构可以随时扩展其他功能,目前来看数据结构还是比较稳定,本文的数据结构理论上来说应该可以支持无限层嵌套,但算法还不能够很好的支持。