绪论
背景
作为一门具有极强表达能力的语言,Common Lisp适合于编译器实现、符号计算等应用。符号计算对于自动做题机器等方面具有广泛的应用。由于Common Lisp代码本身即为定义良好的抽象语法树(AST),因此对于实现编译器、符号计算具有天然的优势。本文基于语义分析器(Sematic Analyzer),以微分运算为例,对符号运算作了一定的探讨,证明了编译原理用于符号计算程序的编写的可行性。
本文的主要内容与意义
本文得出用于符号计算的语义分析器抽象。基于语义分析器,以关于x的四则运算一元表达式以及微分运算为例,对符号运算作了一定的探讨。基于语义分析器,实现了表达式的一种简易的简化功能。并对于一具体的四则运算一元表达式验证了所实现的函数功能。证明了编译原理用于符号计算程序的编写的可行性。
语义分析器抽象
根据微分运算的一个早先版本抽象出一个语义分析器。
(defmacro sematic (expr manage-atom &rest op-list)
"语义分析器抽象,用于符号计算"
`(labels
((computation (,expr)
(let ((expr ,expr))
(if (atom expr)
(funcall ,manage-atom expr) ; manage-atom: 原子处理方法
(let* ((operator-name (car expr))
(arguments (cdr expr))
(evaluated-arguments (mapcar #'computation arguments)))
(case operator-name
,@op-list ; op-list: 运算符列表
(t (error "Not an allowed operator. "))))))))
(computation ,expr)))
该语义分析器以递归方式定义,其作用为自最内向外逐步对S表达式进行处理,从而完成对整个AST的处理。在本文中,用于符号计算的语义分析器的作用是根据给定的AST,基于一定的规则生成新的AST。
语义分析器有三个输入参数,分别为待处理表达式,原子(atom)处理方法,以及运算符列表。
- 待处理表达式:为S表达式,天然为定义良好的AST。由于Common Lisp本身的语法特性,可以直接跳过词法分析、语法分析阶段,直接处理AST,而无需预先生成
- 原子处理方法:Common Lisp中,原子为非列表的表达式,如数字、符号、字符串等。
- 运算符列表:为Common Lisp中
case
语句的一部分。用于定义对于各运算符(+、-、×、÷),分别需要执行的对应操作。
微分计算
基于语义分析器,可以表达微分计算函数。以下分别介绍原子处理方法及运算符列表。
(defun derivation (expr)
"求导函数,获取S表达式,解析S表达式,输出求导后的表达式"
(sematic expr
;; 导数作用于常数、变量规则
(lambda (expr)
(cond
((numberp expr) 0)
((symbolp expr)
(if (eq expr 'x) 1 (error "Only x is allowded symbol. ")))
(t (error "Neither symbol x nor a number. "))))
;; 导数四则运算法则
('+ (cons '+ evaluated-arguments))
(- (cons '- evaluated-arguments))
(* (cond
((eq 2 (length arguments))
(list '+
(list '* (car evaluated-arguments) (cadr arguments))
(list '* (car arguments) (cadr evaluated-arguments))))
; 如果是连乘列表
((< 2 (length arguments))
(derivation
(list '* (car arguments) (cons '* (cdr arguments)))))
(t (error "at least 2 arguments for each operator. "))))
(/ (cond
((eq 2 (length arguments))
(list '/
(list '-
(list '* (car evaluated-arguments) (cadr arguments))
(list '* (car arguments) (cadr evaluated-arguments)))
(list '* (cadr arguments) (cadr arguments))))
; 如果是连除列表
((< 2 (length arguments))
(derivation
(list '/ (car arguments) (cons '* (cdr arguments)))))
(t (error "at least 2 arguments for each operator. "))))))
原子处理方法
首先讨论原子处理方法,其在本函数中表现为微分运算作用于常数、变量的规则。
- 微分作用于常数,所得结果为0。\[(C)'=0 \]
- 微分作用于变量\(x\),所得结果为1。其中,由于仅考虑一元表达式,
x
是表达式中唯一允许的符号。\[(x)'=1 \]
运算符列表
然后讨论运算符列表,其在本函数中表现为导数的四则运算法则
- 导数作用于+、-\[(f(x)\pm g(x))'=f'(x)\pm g'(x) \]
- 导数作用于乘法\[(f(x)g(x))'=f'(x)g+f(x)g'(x) \]
- 导数作用于除法\[\left(\frac{f(x)}{g(x)}\right)'=\frac{f'(x)g(x)-f(x)g'(x)}{g^2(x)} \]
需要注意的是,上述规则默认限定一个运算符仅进行两个数的运算。但实际Common Lisp表达式,可能是一个运算符作用于多个数字的形式,例如
(+ 1 3 5)
(* (+ 1 3 5) (- 1 3 5) (/ 48 2 6) 2)
因此,需要对上述运算法则进行一定的拓展
- 导数作用于+、-\[\left(\sum_{i=1}^k \pm f_i(x)\right)'=\pm \sum_{i=1}^k f'_i(x) \]
- 导数作用于乘法\[\left(\prod_{i=1}^k f_i(x)\right)'=f'_1(x)\prod_{i=2}^k f_i(x) + f_1(x)\left(\prod_{i=2}^k f_i(x)\right)' \]
- 导数作用于除法\[\left(\frac{f_1(x)}{\prod_{i=2}^k f_i(x)}\right)' =\frac{\displaystyle f'_1(x)\prod_{i=2}^k f_i(x)-f_1(x)\left(\prod_{i=2}^k f_i(x)\right)'} {\displaystyle\left(\prod_{i=2}^k f_i(x)\right)^2}\]
其中,导数作用于乘法、除法的处理方式又采用了递归定义。
化简运算
尽管前文定义的微分函数已经具有正常的功能,但会输出未化简结果。需要再次借助语法分析器对AST进行处理,裁剪掉多余的分支。
基于0、1的特殊性的化简
本化简函数基于0和1的特殊性,即
- 加法中0可以消去,若表达式中除某量外全为0,则表达式的值为该量
- 减法的减数中,0可以消去,若减数全为零,则表达式的值为被减数
- 乘法中1可以消去,若表达式中除某量外全为1,则表达式的值为该量;
但须考虑特殊情况:- 当乘法中出现0,则整个表达式值为0(0乘以任何数仍为0)
- 除法的除数中,1可以消去,若除数全为1,则表达式的值为被除数;
但须考虑特殊情况:- 首先,若除数中含有0,则应当只保留0作为除数(认为\(2\div 0\div 1 \div 2\)等于\(2\div 0\))
- 其次,若上述情形不存在,当被除数为0时,则表达式的值为0(0除以任何非零数仍为0)
(defun simplify-01 (expr)
"表达式化简(0和1的特殊性),消去AST多余结构
1. +: 消去所有的0, 若为(+ 0 non-zero-or-0 0),则值为non-zero-or-0
2. -: 消去所有的0,若为(- the-first-elem 0),则值为the-first-elem
3. *: 消去所有的1,若为(* 1 non-one-or-1 1), 则值为non-one-for-1
所有数乘以0仍为零
4. /: 消去所有的1,若为(/ the-first-elem 1),则值为the-first-elem
除数中若有零,应当只保留0;0除以所有数均为0"
`,expr
(sematic expr
;; 化简作用于原子: 无操作
(lambda (expr) expr)
;; 表达式化简规则
('+ (let ((non-zeros
(remove-if (lambda(x) (eq 0 x)) evaluated-arguments)))
(cond ((eq 0 (length non-zeros)) 0)
((eq 1 (length non-zeros)) (car non-zeros))
(t (cons '+ non-zeros)))))
(- (let ((right-non-zeros
(remove-if (lambda(x) (eq 0 x)) (cdr evaluated-arguments)))
(the-first-elem (car evaluated-arguments)))
(cond ((eq 0 (length right-non-zeros)) the-first-elem)
((eq 1 (length right-non-zeros))
(list '- the-first-elem (car right-non-zeros)))
(t (cons '- (cons the-first-elem right-non-zeros))))))
(* (if (member 0 evaluated-arguments)
0
; 常规规则
(let ((non-ones
(remove-if (lambda(x) (eq 1 x)) evaluated-arguments)))
(cond ((eq 0 (length non-ones)) 1)
((eq 1 (length non-ones)) (car non-ones))
(t (cons '* non-ones))))))
(/ (let ((right-non-ones
(remove-if (lambda(x) (eq 0 x)) (cdr evaluated-arguments)))
(the-first-elem (car evaluated-arguments)))
(cond
; 特殊规则
((member 0 right-non-ones) (list '- the-first-elem 0))
((eq 0 the-first-elem) 0)
; 常规规则
((eq 0 (length right-non-ones)) the-first-elem)
((eq 1 (length right-non-ones))
(list '/ the-first-elem (car right-non-ones)))
(t (cons '/ (cons the-first-elem right-non-ones))))))))
基于四则运算特性的化简
该化简函数仍出于设计阶段,尚未完成。
(defun simplify-+-*/ (expr)
"表达式化简(四则运算的特性)
1. +-: + - + - => ( + + ) - ( + )
2. */: * / * / => ( * * ) / ( * )
3. +: 乘法结合律
4. -: 相消
5. /: 约分"
`,expr
'(do-something-here))
示例分析
运行以下示例。
; f(x) = 2*x^2 - 1/x + 5x -1
; f'(x) = 4x + 1/(x^2) +5
(defparameter f '(+ (- (* 2 x x) (/ 1 x) 1) (* 5 x)))
(defparameter |f'| (simplify-01 (derivation f)))
其中,原函数为
,则导函数应当为
运行程序,结果|f'|
的值为(+ (- (* 2 (+ X X)) (/ (- 0 1) (* x x))) 5)
,符合预期。
结论
本文提出了一种基于Common Lisp的微分符号计算功能实现,并对一关于x的四则运算一元表达式示例进行验证,符合预期。验证了编译原理可以用于符号计算的实现。将来可探讨更多化简表达式的函数的规则与实现,有望实现更为完善的符号计算程序。