绪论
背景
作为一门具有极强表达能力的语言,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。
- 微分作用于变量
,所得结果为1。其中,由于仅考虑一元表达式,x
是表达式中唯一允许的符号。
运算符列表
然后讨论运算符列表,其在本函数中表现为导数的四则运算法则
- 导数作用于+、-
- 导数作用于乘法
- 导数作用于除法
需要注意的是,上述规则默认限定一个运算符仅进行两个数的运算。但实际Common Lisp表达式,可能是一个运算符作用于多个数字的形式,例如
(+ 1 3 5) (* (+ 1 3 5) (- 1 3 5) (/ 48 2 6) 2)
因此,需要对上述运算法则进行一定的拓展
- 导数作用于+、-
- 导数作用于乘法
- 导数作用于除法
其中,导数作用于乘法、除法的处理方式又采用了递归定义。
化简运算
尽管前文定义的微分函数已经具有正常的功能,但会输出未化简结果。需要再次借助语法分析器对AST进行处理,裁剪掉多余的分支。
基于0、1的特殊性的化简
本化简函数基于0和1的特殊性,即
- 加法中0可以消去,若表达式中除某量外全为0,则表达式的值为该量
- 减法的减数中,0可以消去,若减数全为零,则表达式的值为被减数
- 乘法中1可以消去,若表达式中除某量外全为1,则表达式的值为该量;
但须考虑特殊情况:- 当乘法中出现0,则整个表达式值为0(0乘以任何数仍为0)
- 除法的除数中,1可以消去,若除数全为1,则表达式的值为被除数;
但须考虑特殊情况:- 首先,若除数中含有0,则应当只保留0作为除数(认为
等于 ) - 其次,若上述情形不存在,当被除数为0时,则表达式的值为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的四则运算一元表达式示例进行验证,符合预期。验证了编译原理可以用于符号计算的实现。将来可探讨更多化简表达式的函数的规则与实现,有望实现更为完善的符号计算程序。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界