Նախապես ասեմ, որ արտահայտություները սահմանափակված են միայն գումարում, հանում, բազմապատկում և բաժանում բինար գործողություններով, իսկ դիֆերենցիալը հաշվող
differentiate ֆունկցիան սպասում է, որ իր մուտքին տվելու է արտահայտության պրեֆիքսային ներկայացումն ու այն փոփոխականը, ըստ որի կատարվում է դիֆերենցումը։Արտահայտությունների դիֆերենցիալը հաշվելու համար օգտագործվում են հետևյալ բանաձևերը.
- \(C\) հաստատունի համար. \[\frac{dC}{dx}=0,\]
- \(x\) փոփոխականի համար. \[\frac{dx}{dx}=1,\]
- \(u+v\) գումարի համար. \[\frac{d(u+v)}{dx}=\frac{du}{dx}+\frac{dv}{dx},\]
- \(u\cdot v\) արտադրյալի համար. \[\frac{d(uv)}{dx}=\frac{du}{dx}v+\frac{dv}{dx}u,\]
- \(\frac{u}{v}\) քանորդի համար. \[\frac{d}{dx}\Big(\frac{u}{v}\Big)=\frac{\frac{du}{dx}v-\frac{dv}{dx}u}{v^2},\]
differentiate ռեկուրսիվ պրոցեդուրայում պարզապես ծրագրավորված են նշված բանաձևերը։
proc differentiate { src var } {
set H [lindex $src 0]
# constant
if [regexp {\d+} $H] {
return 0
}
# variable
if [regexp {\w[\w\d]*} $H] {
if [string equal $H $var] {
return 1
} else {
return 0
}
}
# addition, subtraction
if [regexp {(\+|\-)} $H] {
set L [differentiate [lindex $src 1] $var]
set R [differentiate [lindex $src 2] $var]
return [addi $H $L $R]
}
# multiplication, division
if [regexp {(\*|\/)} $H] {
set A [lindex $src 1]
set B [lindex $src 2]
set L [muli "*" [differentiate $A $var] $B]
set R [muli "*" [differentiate $B $var] $A]
if [string equal {*} $H] {
return [addi "+" $L $R]
}
return [muli "/" [addi "-" $L $R] [muli "*" $B $B]]
}
}
Բացի դիֆերենցիալի հաշվումից այս պրոցեդուրան կատարում է նաև մի քանի պարզեցումներ՝ հաշվի առնելով, որ \(0\cdot x=0\), \(1\cdot x=x\) և \(0 + x=x\)։ Այս պարզեցումները կատարվում են addi, muli և numeq պրոցեդուրաներով։
proc numeq { n v } {
if [regexp {^\d+$} $n] {
return [expr $n == $v]
}
return false
}
proc addi { o a b } {
if [numeq $a 0] { return $b }
if [numeq $b 0] { return $a }
if {[regexp {^\d+$} $a] && [regexp {^\d+$} $b]} {
return [expr $a $o $b]
}
return [list $o $a $b]
}
proc muli { o a b } {
if {[numeq $a 0] || [numeq $b 0]} { return 0 }
if [numeq $a 1] { return $b }
if [numeq $b 1] { return $a }
if {[regexp {^\d+$} $a] && [regexp {^\d+$} $b]} {
return [expr $a $o $b]
}
return [list $o $a $b]
}
Սա շատ լավ է։ Կարելի է կառուցել արտահայտությունների պրեֆիքսային ներկայացման օրինակներ և համոզվել, որ
differentiate պրոցեդուրան իր անելիքն անում է։ Բայց հետաքրքիր խնդիր է նաև ինֆիքսային գրառմամբ տրված արտահայտությունից պրեֆիքսային տեսքը կառուցելը։ Այդ խդիրը լուծելու համար ես գրել եմ ստանդարտ շարահյուսական անալիզատոր, որը կառուցում է տրված արտահայտության աբստրակտ քերականական ծառը Tcl լեզվի ցուցակի տեսքով, որն էլ հենց արտահայտության պրեֆիքսային ներկայացում է։ Ահա այդ կոդը.
namespace eval Parser {
variable tokens [list]
variable position -1
variable current {}
# սա կարելի է ասել, որ լեքսիկական անալիզատորն է
proc tokenizer { src } {
set temp [regsub -all -- {(\+|\-|\*|\/|\(|\))} $src { \1 }]
set temp [string trim [regsub -all -- {\s+} $temp { }]]
return [split $temp { }]
}
# ստուգում է հերթական սիմվոլը, և եթե այն համաատասխանում է
# սպասվածին, ապա փոխարինում է հաջորդով, հակառակ դեպքում
# գեներացնում է քերականական սխալ
proc next { tok } {
variable tokens
variable current
variable position
if [regexp $tok $current] {
incr position
set current [lindex $tokens $position]
} else {
error "Syntax error"
}
}
# վերլուծում է գումարման ու հանման գործողությունները
proc parseExpr { } {
variable current
set R [parseTerm]
while {({+} eq $current) || ({-} eq $current)} {
set op $current
next {[\+\-]}
set R [list $op $R [parseTerm]]
}
return $R
}
# վերլուծում է բազմապատկման ու բաժանման գործողությունները
proc parseTerm { } {
variable current
set R [parseFactor]
while {({*} eq $current) || ({/} eq $current)} {
set op $current
next {[\*\/]}
set R [list $op $R [parseFactor]]
}
return $R
}
# վերլուծում է հաստատունները, փոփոխականները և
# խմբավորման փակագծերը
proc parseFactor { } {
variable current
set res {}
if [regexp {[0-9]+} $current] {
set res $current
next {[0-9]+}
} elseif [regexp {\w[\w\d]*} $current] {
set res $current
next {\w[\w\d]*}
} elseif [string equal {(} $current] {
next {[\(]}
set res [parseExpr]
next {[\)]}
}
return $res
}
# այս պրոցեդուրայից է սկսվում անալիզատորի աշխատանքը,
# այն ստանում է արտահայտության տեքստը, վերլուծում է ու
# վերադարձնում է նրա պրեֆիքսային ներկայացումը
proc parse { src } {
variable tokens
variable position
variable current
set tokens [tokenizer $src]
lappend tokens EOS
set position -1
set current {}
next {}
return [parseExpr]
}
}
prefixToInfix պրոցեդուրան լուծում է հակառակ խնդիրը։ Այն իր արգումենտում ստանում է արտահայտության պրեֆիքսային ներկայացումը և վերադարձնում է ինֆիքսայինը։ Այս տարբերակով, իհարկե, այն արտահայտության մեջ դնում է ավելորդ փակագծեր, բայց այդ թերությունը հեշտությամբ կարելի է շտկել։
proc prefixToInfix { exp } {
set H [lindex $exp 0]
if [regexp {^\d+$} $H] {
return $H
}
if [regexp {\w[\w\d]*} $H] {
return $H
}
if [regexp {(\+|\-|\*|\/)} $H _ op] {
set L [prefixToInfix [lindex $exp 1]]
set R [prefixToInfix [lindex $exp 2]]
return "($L $op $R)"
}
}
No comments:
Post a Comment