求表达式偏导(输出的是表达式)(Perl实现)

这是学校perl课程结束时的大作业。
开始思考前,我上网搜寻了下,发现网上相应资料几乎没有,大多数都是求表达式某个点的导数值。而作业要求是求一个表达式的偏导式子,即输出也要是式子。
没办法,只能自己思考了(最终版本代码在最后(带注释,可直接跳跃看最终版代码,代码600行,建议复制到编辑器中观看))
开始阅读前建议点击下面链接观看3.5, 3.6, 3.7, 9.5, 9.6。因为写的很简单,不了解下数据结构的知识可能看不懂
B站数据结构视频

  1. 最开始思路

    对于表达式求导,第一个目标是先做出来加减乘除的。由于观看了北大的表达式求值视频,所以一开始我就想通过建立表达式树来求导。
    而建立表达式树又需要全括号形式(有多少个运算符,就有多少括号),所以最开始直接处理的是全括号形式式子。
    首先建立一个树的结点类。
    结点

package Node;   #表达式树的结点类
use strict;

sub new {
	my $class = shift();
	my $self = {}; 
	$self->{"left"} = shift();      #左子结点
	$self->{"right"} = shift();     #右子结点
	$self->{"p"} = shift();         #父节点
	$self->{"key"} = shift();       #储存常量、变量、运算符等信息
	$self->{"type"} = shift();      #结点分两类,op(运算符结点)、leaf(叶子结点)(常量与变量)
	$self->{"derivative"} = shift();#储存当前节点及其子树的导数
	bless $self, $class;
	return $self;
}

sub setLeft{    #设置结点左结点
        my ($self) = @_;
        my $left = Node->new(undef,undef,$self,undef,undef,undef);
        $self->{"left"} = $left;
}

sub setRight{   #设置结点右结点
        my ($self) = @_;
        my $right = Node->new(undef,undef,$self,undef,undef,undef);
        $self->{"right"} = $right;
}
1;
根据表达式建立表达式树,这里建议观看北京大学python版数据结构的表达式树构建。代码如下
sub buildParseTree{
        my ($expresssion) = @_;
        my @e = split //,$expresssion;
        for(@e){
                if($_ eq '('){
                        $cur->setLeft();
                        $cur = $cur->{"left"};
                }elsif($_ eq ')'){
                        $cur = $cur->{"p"};
                }elsif($_ =~ /[\+\-\*\/\^]/){
                        $cur->{"key"} = $_;
                        $cur->{"type"} = "op";
                        $cur->setRight();
                        $cur = $cur->{"right"};
                }elsif($_ =~ /\w/){
                        $cur->{"key"} = $_;
                        $cur->{"type"} = "leaf";
                        $cur = $cur->{"p"};
                }else{
                        print "Incorrect expression input\n";
                }
        }
}
然后对表达式树求导。求导后输出全括号形式结果,但是结果括号太多,还有(0-0)等很多没有必要的结构,代码如下。subExpression函数是编写的返回以给定结点为根的表达式
sub derivation{
        my ($d_cur,$x) = @_;
        if($d_cur->{"type"} eq "op"){
                my $d_l = &derivation($d_cur->{"left"},$x);
                my $d_r = &derivation($d_cur->{"right"},$x);
                if($d_cur->{"key"} eq "+"){
                        $d_cur->{"derivative"} = "($d_l+$d_r)";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "-"){
                        $d_cur->{"derivative"} = "($d_l-$d_r)";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "*"){
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        $d_cur->{"derivative"} = "(($d_l*$e_r)+($d_r*$e_l))";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "/"){
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        $d_cur->{"derivative"} = "((($d_l*$e_r)-($d_r*$e_l))/($e_r^2))";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "^"){
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        $d_cur->{"derivative"} = "($e_r*($e_l^($e_r-1)))";
                        return $d_cur->{"derivative"};
                }
        }elsif($d_cur->{"type"} eq "leaf"){
                if($d_cur->{"key"} eq "$x"){
                      $d_cur->{"derivative"} = 1;
                      return   $d_cur->{"derivative"};
                }else{
                      $d_cur->{"derivative"} = 0;
                      return   $d_cur->{"derivative"};  
                }
        } 
}

运行结果:
第一个版本

  1. 进一步思考
    加入了sin,cos,ln等的求导,想法是将sin(x+y)变(x+y)sin(x+y)把sin等看做运算符。这时处理并不好,这时我是采用正则来处理这些。导致复杂形式的处理不好。此时输入与输出还都是全括号形式。但是简化了求导结果,如(0-0)一些情况没了
    就不放代码了,代码太多。
  2. 最终版本
    最后,我采用遍历方式匹配sin这些后面的括号,让他们括号能够匹配上,不会出现正则匹配到(()))这种左右括号个数不一样的情况,加入了建立表达式树前的处理(如负数的处理)。且让输入能够是部分括号形式(方法是把部分括号表达式转化为后缀表达式,再由后缀表达式转化为全括号表达式)。对于输出也根据优先级处理使变成部分括号形式。
    其实一共写了5…6个版本。

最终代码如下:

#!usr/bin/perl -w
use strict;
use encoding 'utf8',STDIN=>'gb2312', STDOUT=>'gb2312'; 

#优先级说明:"+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3   输入表达式的时候根据优先级,必要括号不能少
#对于sin|cos|ln的处理,开始想到的是正则,但是(sin((())))这种n对括号、并且外面还有括号包住的麻烦的形式还是无能为力,就改成了遍历形式,比较万能
#处理过程
        #1.对输入的表达式处理
                #(1)去空格,处理负号,处理大于9数字等
                #(2)处理sin|cos|ln(让他们在数组中只占一个位子、并且把他们变成和+等一样的双目运算符,便于建立表达式树)
                #(3)表达式变后缀表达式,后缀再变成全括号表达式,让能建立表达式树
        #2.表达式建立表达式树
        #3.对表达式树求导
        #4.对求导结果(全括号形式)建立导数表达式树
        #5.化简导数表达式树(处理0*0,1*0等10几种情况)
        #6.输出去掉部分括号的求导结果
#输入的表达式注意点
        #1.exp()写成e^()形式
        #2.所有单个字符的不要加括号,如sinx别写成sin(x),e^x别写成e^(x)
        #3.负数要加括号,如(-1)、(-x)
        #4.变量只能为一个字母,不能写成x1等(觉得这个没必要优化)

package Node;   #表达式树的结点类
use strict;

sub new {
	my $class = shift();
	my $self = {}; 
	$self->{"left"} = shift();      #左子结点
	$self->{"right"} = shift();     #右子结点
	$self->{"p"} = shift();         #父节点
	$self->{"key"} = shift();       #储存常量、变量、运算符等信息
	$self->{"type"} = shift();      #结点分两类,op(运算符结点)、leaf(叶子结点)(常量与变量)
	$self->{"derivative"} = shift();#储存当前节点及其子树的导数
	bless $self, $class;
	return $self;
}

sub setLeft{    #设置结点左结点
        my ($self) = @_;
        my $left = Node->new(undef,undef,$self,undef,undef,undef);
        $self->{"left"} = $left;
}

sub setRight{   #设置结点右结点
        my ($self) = @_;
        my $right = Node->new(undef,undef,$self,undef,undef,undef);
        $self->{"right"} = $right;
}
1;


#用于实验的带有各种元素的表达式,非全括号形式
my $expresssion="3*x^20+4*x/(y+1)+1+y^(3*x)+3*sin((x+y)*x^2)+e^x+lnx+(-x)";    #输入的表达式
print "您输入的非全括号形式的式子: $expresssion\n\n";
my $x = "x"; #要改变求偏导的对象,改这里,如可改为y

#处理表达式的子程序
sub dealWithExpresssion{
	my $e = shift @_;
	$e =~ s/\(\-/\(0\-/;	#处理负号
	$e =~ s/\s+//;		#处理空格
	my @e = split //,$e;
	my @e_merge;            #储存第一轮处理后的数据
	
	#下面for循环处理sin|cos|ln 与 连续数字让他们只占一个位子
	for(my $i = 0; $i < $#e + 1;){#遍历分割后的数组     
                my ($s1, $s2, $s3) = ($e[$i], $e[$i+1], $e[$i+2]);
                if(($s1 eq "s")&&($s2 eq "i")&&($s3 eq "n")){    #合并sin
                        push @e_merge,"sin";
                        $i += 3;
                }elsif(($s1 eq "c")&&($s2 eq "o")&&($s3 eq "s")){#合并cos
                        push @e_merge,"cos";
                        $i += 3;
                }elsif(($s1 eq "l")&&($s2 eq "n")){             #合并ln
                        push @e_merge,"ln";
                        $i += 2;
                }elsif($s1 =~ /\d/){  #合并大于9的数字
                        my $num = $s1;
                        for(my $j = $i+1;$j<$#e+1;$j++){
                                $i = $j;
                                if($e[$j] =~ /\d/){$num .= $e[$j];}
                                else{last;}
                        }
                        push @e_merge,$num;
                }else{
                        push @e_merge,$e[$i];
                        $i += 1;
                }
	}
	# print "@e_merge\n";
	
	my @e_merge_1;  #储存第二轮处理后的数据
	#下面的for循环处理sin|cos|ln让后期能够建立表达式树,做法为将sin|cos|ln转变为双目运算符
	#这里最开始想到的正则,但是当(1+sin(x*(x*(x^y))))这种复杂sin,正则处理不会(括号太多,匹配不到对的)
	for(my $i = 0; $i < $#e_merge + 1;){
		if($e_merge[$i] =~ /sin|cos|ln/){
			my $op = $e_merge[$i];
			if($e_merge[$i+1] eq "("){      #当sin|cos|ln紧跟着(时,代表是要进行括号的匹配。直到右括号个数等于左括号时完成遍历
				my $l_brackets = 0;
				my $r_brackets = 0;
				my $modulus;
				for(my $j = $i+1;$j<$#e_merge+1;$j++){
					$i = $j;
					if($e_merge[$j] eq "("){        #为左括号时,代表左括号个数变量加一
						++$l_brackets;
						$modulus .= $e_merge[$j];
					}elsif($e_merge[$j] eq ")"){    #为右括号时,代表左括号个数变量加一,并且当数量等于左括号时,结束循环
						++$r_brackets;
						$modulus .= $e_merge[$j];
						if($r_brackets == $l_brackets){last;}
					}else{$modulus .= $e_merge[$j];}
				}
				$i += 1;
				push @e_merge_1,"(";
				for(split //,$modulus){
					push @e_merge_1,$_;
				}
				push @e_merge_1,$op;
				for(split //,$modulus){
					push @e_merge_1,$_;
				}
				push @e_merge_1,")";
			}else{                  #运行到这里代表是sinx这种简单形式
				push @e_merge_1,"(";
				push @e_merge_1,$e_merge[$i+1];
				push @e_merge_1,$op;
				push @e_merge_1,$e_merge[$i+1];
				push @e_merge_1,")";
				$i += 2;
			}
                
		}else{
			push @e_merge_1,$e_merge[$i];
			$i += 1;
		}
	}
	# print "@e_merge_1\n";
	
	#下面的操作是把表达式转化为全括号表达式
		#1.转化为后缀表达式
		#2.后缀转全括号表达式
		
	#1.转化为后缀表达式,基本按北大视频来的
	my %grade = ("(" => 0, "+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3, "sin" => 4, "cos" => 4, "ln" => 4);#记录优先级
	my @stack;      #数组可以做栈
	my @e_merge_2;  #储存第三轮处理后的数据
	for(@e_merge_1){
		if($_ =~ /[\+\-\*\/\^]/){
			if($#stack >= 0){
				my $temporary = pop @stack;
				push @stack,$temporary;
				while(($#stack >= 0)&&($grade{"$temporary"} >= $grade{"$_"})){
                                        $temporary = pop @stack;
                                        push @e_merge_2,$temporary;
                                        if($#stack >= 0){
                                                $temporary = pop @stack;
                                                push @stack,$temporary;
                                        }
                                }
			}
			push @stack,$_;
		}elsif($_ =~ /sin|cos|ln/){
			push @stack,$_;
		}elsif($_ eq "("){
			push @stack,$_;
		}elsif($_ eq ")"){
			my $pop = pop @stack;
			while($pop ne "("){
				push @e_merge_2,$pop;
				$pop = pop @stack;
			}
		}else{
			push @e_merge_2,$_;
		}
	}
	while($#stack >= 0){
		my $temporary = pop @stack;
		push @e_merge_2,$temporary;
	}
	# print "@e_merge_2\n";
	
	#2.后缀转全括号表达式,就是把北大视频的弹栈计算改成了弹栈加括号
	for(@e_merge_2){
		if(($_ =~ /sin|cos|ln/)||($_ =~ /[\+\-\*\/\^]/)){
			my $r = pop @stack;
			my $l = pop @stack;
			push @stack,"($l$_$r)";
		}else{
			push @stack,$_;
		}
	}
	my $full_brackets = pop @stack;
	return $full_brackets;
}

$expresssion = &dealWithExpresssion($expresssion);
print "处理过并且是全括号形式的式子: $expresssion\n\n";

#建立表达式树子程序,也基本按北大视频来的,求导和化简求导都要建立表达式树
sub buildParseTree{     
        my ($expresssion,$cur) = @_;
        my @e = split //,$expresssion;          #分割为数组
        my @e_merge;                  #储存处理过后的数组
        for(my $i = 0;$i<$#e+1;){     #处理sin|cos|ln与连续数字让他们只占一个位子
                my ($s1,$s2,$s3) = ($e[$i],$e[$i+1],$e[$i+2]);
                if(($s1 eq "s")&&($s2 eq "i")&&($s3 eq "n")){
                        push @e_merge,"sin";
                        $i += 3;
                }elsif(($s1 eq "c")&&($s2 eq "o")&&($s3 eq "s")){
                        push @e_merge,"cos";
                        $i += 3;
                }elsif(($s1 eq "l")&&($s2 eq "n")){
                        push @e_merge,"ln";
                        $i += 2;
                }elsif($s1 =~ /\d/){
                        my $num = $s1;
                        for(my $j = $i+1;$j<$#e+1;$j++){
                                $i = $j;
                                if($e[$j] =~ /\d/){$num .= $e[$j];}
                                else{last;}
                        }
                        push @e_merge,$num;
                }else{
                        push @e_merge,$e[$i];
                        $i += 1;
                }
        }
        #建立表达式树,和北大视频中一样
        for(@e_merge){        
                if($_ eq '('){
                        $cur->setLeft();
                        $cur = $cur->{"left"};
                }elsif($_ eq ')'){
                        $cur = $cur->{"p"};
                }elsif($_ =~ /[\+\-\*\/\^]/){
                        $cur->{"key"} = $_;
                        $cur->{"type"} = "op";
                        $cur->setRight();
                        $cur = $cur->{"right"};
                }elsif($_ =~ /sin|cos|ln/){     #sin|cos|ln与+-*/一样处理
                        $cur->{"key"} = $_;
                        $cur->{"type"} = "op";
                        $cur->setRight();
                        $cur = $cur->{"right"};
                }elsif($_ =~ /\w+/){
                        $cur->{"key"} = $_;
                        $cur->{"type"} = "leaf";
                        $cur = $cur->{"p"};
                }else{
                        print "全括号表达式有误\n";
                }
        }
}

my $e_root = Node->new(undef,undef,undef,undef,undef,undef);    #表达式树根
$e_root->{"p"} = $e_root;                                       #树根父亲为自己
&buildParseTree($expresssion,$e_root);                          #建立输入式子的表达式树

#中序遍历,写这个是观察树建立情况时用的
# sub printTree{          
        # my ($self) = @_;
        # if(!(defined $self)){
		# return;
	# }
	# &printTree($self->{"left"});
	# my $m1 = $self->{"key"};
	# my $m2 = $self->{"derivative"};
	# if(defined $m2){
	        # print "$m1 $m2 ";
	# }else{ print "$m1 "; }
	# &printTree($self->{"right"});
# }
#遍历后的$cur为树跟的父亲,上面设置了它的父亲为自己,所以打印树的时候传入$cur
# &printTree($e_root);


#把 表达式树 还原成 全括号形式 的子程序,即相当于buildParseTree的相反
sub subExpression{      
      my ($d_cur) = @_;  
      if($d_cur->{"type"} eq "op"){            #结点为运算符类型时
              if($d_cur->{"key"} =~ /sin|cos|ln/){#当为sin|cos|ln时,只需向一个方向递归(因为前面处理时把他们右边的复制到了左边),且返回值与正常运算符不同
                      my $e_r = &subExpression($d_cur->{"right"});
                      my $op = $d_cur->{"key"};
                      return "$op$e_r";
              }else{
                      my $e_l = &subExpression($d_cur->{"left"});
                      my $e_r = &subExpression($d_cur->{"right"});
                      my $op = $d_cur->{"key"};
                      return "($e_l$op$e_r)";
              }
      }elsif($d_cur->{"type"} eq "leaf"){     #结点为叶子类型时
              if($d_cur->{"key"} =~ /\-/ ){     #这里是为了防止后面求导简化后有负数形式存在
                      my $key = $d_cur->{"key"};
                      return "(0$key)";
              }else{
                      return $d_cur->{"key"};
              }
      }
}

#求导子程序,要传2个参数,结点与求偏导的变量,也先分为"op"和"leaf"分别处理,其中op又分为很多情况
sub derivation{
        my ($d_cur,$x) = @_;
        if($d_cur->{"type"} eq "op"){
                my $d_l = &derivation($d_cur->{"left"},$x);     #左子树递归求导结果
                my $d_r = &derivation($d_cur->{"right"},$x);    #右子树递归求导结果
                #以下分不同操作符进行不同规则求导
                if($d_cur->{"key"} eq "+"){
                        $d_cur->{"derivative"} = "($d_l+$d_r)";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "-"){
                        $d_cur->{"derivative"} = "($d_l-$d_r)";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "*"){
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        $d_cur->{"derivative"} = "(($e_r*$d_l)+($e_l*$d_r))";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "/"){
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        $d_cur->{"derivative"} = "((($d_l*$e_r)-($d_r*$e_l))/($e_r^2))";
                        return $d_cur->{"derivative"};
                }elsif($d_cur->{"key"} eq "^"){ #幂函数与指数函数都是^符号,分情况讨论
                        my $e_l = &subExpression($d_cur->{"left"});
                        my $e_r = &subExpression($d_cur->{"right"});
                        if($e_l =~ /$x/){       #x^a形式
                                $d_cur->{"derivative"} = "($e_r*($e_l^($e_r-1)))";
                                return $d_cur->{"derivative"};
                        }elsif(($e_l eq "e") && ($e_r =~ /$x/)){    #e^x形式
                                $d_cur->{"derivative"} = "(($e_l^$e_r)*$d_r)";
                                return $d_cur->{"derivative"};
                        }elsif($e_r =~ /$x/){   #a^x形式
                                $d_cur->{"derivative"} = "((ln$e_l*($e_l^$e_r))*$d_r)";
                                return $d_cur->{"derivative"};
                        }else{                  #常数
                                $d_cur->{"derivative"} = 0;
                                return 0;
                        } 
                }elsif($d_cur->{"key"} eq "sin"){#sin求导
                        my $e_r = &subExpression($d_cur->{"right"});#sin|cos|ln是处理过的,只取一边
                        if($e_r =~ /$x/){       #sin式子中包含要求偏导的变量
                                $d_cur->{"derivative"} = "(cos$e_r*$d_r)";
                                return $d_cur->{"derivative"};
                        }else{                  #常量
                                $d_cur->{"derivative"} = 0;
                                return 0;
                        }
                }elsif($d_cur->{"key"} eq "cos"){#cos求导
                        my $e_r = &subExpression($d_cur->{"right"});
                        if($e_r =~ /$x/){       #cos式子中包含要求偏导的变量
                                $d_cur->{"derivative"} = "((0-sin$e_r)*$d_r)";
                                return $d_cur->{"derivative"};
                        }else{                  #常量
                                $d_cur->{"derivative"} = 0;
                                return 0;
                        }
                }elsif($d_cur->{"key"} eq "ln"){#ln求导
                        my $e_r = &subExpression($d_cur->{"right"});
                        if($e_r =~ /$x/){       #ln式子中包含要求偏导的变量
                                $d_cur->{"derivative"} = "((1/$e_r)*$d_r)";
                                return $d_cur->{"derivative"};
                        }else{                  #常量
                                $d_cur->{"derivative"} = 0;
                                return 0;
                        }
                        
                }else{
                        print "建立的表达式树有误\n";
                }
        }elsif($d_cur->{"type"} eq "leaf"){#叶子结点分2种情况
                if($d_cur->{"key"} eq "$x"){#包含要求偏导的变量
                      $d_cur->{"derivative"} = 1;
                      return   $d_cur->{"derivative"};
                }else{                      #不包含要求偏导的变量
                      $d_cur->{"derivative"} = 0;
                      return   $d_cur->{"derivative"};  
                }
        } 
}

my $e_derivation = &derivation($e_root,"$x");#储存未化简的求导全括号结果
print "全括号形式的求导结果: $e_derivation\n\n";


my $d_root = Node->new(undef,undef,undef,undef,undef,undef);#求导结果的表达式树根
$d_root->{"p"} = $d_root;
$e_derivation = &dealWithExpresssion($e_derivation);#处理全括号形式的求导结果
&buildParseTree($e_derivation,$d_root);             #建立求导结果的表达式树

#简化求导结果的子程序,只遍历op类型结点,根据其左右结点类型分4大类,每一类又细分
#主要化简0*x,1*x,1+3等10几种情况
sub simplify{
      my ($d_cur) = @_;
      my $l = $d_cur->{"left"};
      my $r = $d_cur->{"right"};
      my $type_l = $l->{"type"};
      my $type_r = $r->{"type"};
      
      if(($type_l eq "leaf")&&($type_r eq "leaf")){#左右结点都为叶子结点时
              if($d_cur->{"key"} eq "+"){
                      if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){     #数字+数字
                              $d_cur->{"key"} = $l->{"key"}+$r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($l->{"key"} eq "0"){                               #0+变量
                              $d_cur->{"key"} = $r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($r->{"key"} eq "0"){                               #变量+0
                              $d_cur->{"key"} = $l->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }
              }elsif($d_cur->{"key"} eq "-"){
                      if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){     #数字-数字
                              $d_cur->{"key"} = $l->{"key"}-$r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($l->{"key"} eq "0"){                               #0-数字
                              $d_cur->{"key"} = $r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($r->{"key"} eq "0"){                               #数字-0
                              $d_cur->{"key"} = $l->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }
              }elsif($d_cur->{"key"} eq "*"){
                      if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){     #数字*数字
                              $d_cur->{"key"} = $l->{"key"}*$r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif( ($l->{"key"} eq "0") || ($r->{"key"} eq "0") ){   #左右中出现0
                              $d_cur->{"key"} = 0;
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($l->{"key"} eq "1"){                               #1*变量
                              $d_cur->{"key"} = $r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($r->{"key"} eq "1"){                               #变量*1
                              $d_cur->{"key"} = $l->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }
              }elsif($d_cur->{"key"} eq "/"){                                   #0/任何
                      if($l->{"key"} eq "0"){
                              $d_cur->{"key"} = 0;
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }
              }elsif($d_cur->{"key"} eq "^"){
                      if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){     #数字^数字
                              $d_cur->{"key"} = $l->{"key"}**$r->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($l->{"key"} eq "1"){                               #1^任意
                              $d_cur->{"key"} = 1;
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }elsif($r->{"key"} eq "1"){                               #任意^1....输入时可以避免输入任意^1产生任意^0的情况
                              $d_cur->{"key"} = $l->{"key"};
                              $d_cur->{"type"} = "leaf";
                              ($l,$r) = (undef,undef);
                      }
              }
              return;
      }elsif(($type_l eq "op")&&($type_r eq "op")){#左右结点都为运算符结点时
              &simplify($l);
              &simplify($r);
      }else{
              if($type_l eq "leaf"){            #右结点为运算符结点,左结点为叶子结点时
                      &simplify($r);
                      if($d_cur->{"key"} eq "+"){
                              if($l->{"key"} eq "0"){
                                      $d_cur->{"key"} = $r->{"key"};            #0+式子
                                      $d_cur->{"type"} = $r->{"type"};
                                      $d_cur->{"left"} = $r->{"left"};
                                      $d_cur->{"right"} = $r->{"right"}; 
                              }
                      }elsif($d_cur->{"key"} eq "*"){                           
                              if($l->{"key"} eq "0"){                           #0*式子
                                      $d_cur->{"key"} = 0;
                                      $d_cur->{"type"} = "leaf";
                                      ($l,$r) = (undef,undef);
                              }elsif($l->{"key"} eq "1"){                       #1*式子
                                      $d_cur->{"key"} = $r->{"key"};
                                      $d_cur->{"type"} = $r->{"type"};
                                      $d_cur->{"left"} = $r->{"left"};
                                      $d_cur->{"right"} = $r->{"right"};
                              }
                      }elsif($d_cur->{"key"} eq "/"){                           #0/式子
                              if($l->{"key"} eq "0"){
                                      $d_cur->{"key"} = 0;
                                      $d_cur->{"type"} = "leaf";
                                      ($l,$r) = (undef,undef);
                              }
                      }elsif($d_cur->{"key"} eq "^"){                           
                              if($l->{"key"} eq "1"){                           #1^式子
                                      $d_cur->{"key"} = 1;
                                      $d_cur->{"type"} = "leaf";
                                      ($l,$r) = (undef,undef);
                              }
                      }
              }else{                     #左结点为运算符结点,右结点为叶子结点时
                      &simplify($l);
                      if($d_cur->{"key"} eq "+"){                                   
                              if($r->{"key"} eq "0"){                           #式子+0
                                      $d_cur->{"key"} = $l->{"key"};
                                      $d_cur->{"type"} = $l->{"type"};
                                      $d_cur->{"left"} = $l->{"left"};
                                      $d_cur->{"right"} = $l->{"right"};
                              }
                      }elsif($d_cur->{"key"} eq "*"){
                              if($r->{"key"} eq "0"){                           #式子*0
                                      $d_cur->{"key"} = 0;
                                      $d_cur->{"type"} = "leaf";
                                      ($l,$r) = (undef,undef);
                              }elsif($r->{"key"} eq "1"){                       #式子*1
                                      $d_cur->{"key"} = $l->{"key"};
                                      $d_cur->{"type"} = $l->{"type"};
                                      $d_cur->{"left"} = $l->{"left"};
                                      $d_cur->{"right"} = $l->{"right"};
                              }
                      }elsif($d_cur->{"key"} eq "-"){                           
                              if($r->{"key"} eq "0"){                           #式子-0
                                      $d_cur->{"key"} = $l->{"key"};
                                      $d_cur->{"type"} = $l->{"type"};
                                      $d_cur->{"left"} = $l->{"left"};
                                      $d_cur->{"right"} = $l->{"right"};
                              }
                      }elsif($d_cur->{"key"} eq "^"){                           
                              if($r->{"key"} eq "1"){                           #式子^1
                                      $d_cur->{"key"} = $l->{"key"};
                                      $d_cur->{"type"} = $l->{"type"};
                                      $d_cur->{"left"} = $l->{"left"};
                                      $d_cur->{"right"} = $l->{"right"};
                              }
                      }
              }
      }
}

&simplify($d_root);
my $simplify_e_derivation = &subExpression($d_root);
for(1..10){#化简一边可能会有新的出现,多化简几次,这里没有想到很好的控制简化结束的方法,所以就粗暴的用了循环10次
        &simplify($d_root);
        $simplify_e_derivation = &subExpression($d_root);
}

#去括号子程序(根据运算符优先级),相对于subExpression函数有所变动
my %grade = ("+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3, "sin" => 4, "cos" => 4, "ln" => 4);#记录优先级
sub super_subExpression{      
      my ($d_cur) = @_;  
      if($d_cur->{"type"} eq "op"){     #结点为运算符类型时
              if($d_cur->{"key"} =~ /sin|cos|ln/){#当为sin|cos|ln时,只需向一个方向递归,且返回值与正常运算符不同
                      my $e_r = &super_subExpression($d_cur->{"right"});
                      my $op = $d_cur->{"key"};
                      if(length $e_r == 1){     #sinx这种情况不需要加括号
                              return "$op$e_r";
                      }else{return "$op($e_r)";}
              }else{
                      my $e_l = &super_subExpression($d_cur->{"left"});
                      my $e_r = &super_subExpression($d_cur->{"right"});
                      my $op = $d_cur->{"key"};
                      if($op =~ /[\*\/]/){      #当运算符结点的左或右结点为优先级比他们低的运算符结点时,左或右结点要加括号
                              if($d_cur->{"left"}->{"key"} =~ /[\+\-]/){$e_l = "($e_l)"}
                              if($d_cur->{"right"}->{"key"} =~ /[\+\-]/){$e_r = "($e_r)"}
                              return "$e_l$op$e_r";
                      }elsif($op eq "^"){
                              if($d_cur->{"left"}->{"key"} =~ /[\+\-\*\/]/){$e_l = "($e_l)"}
                              if($d_cur->{"right"}->{"key"} =~ /[\+\-\*\/]/){$e_r = "($e_r)"}
                              return "$e_l$op$e_r";
                      }else{
                              return "$e_l$op$e_r";
                      }
              }
      }elsif($d_cur->{"type"} eq "leaf"){#结点为叶子类型时
              if($d_cur->{"key"} =~ /\-/ ){
                      my $key = $d_cur->{"key"};
                      return "($key)";
              }else{
                      return $d_cur->{"key"};
              }
      }
}
 $simplify_e_derivation = &super_subExpression($d_root);
print "化简并且去掉部分括号后的偏导数:$simplify_e_derivation\n\n";
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值