F#库FParsec实现求导符号计算

简介: 利用F#实现对用户输入一个文本类型的数学公式进行解析,且自动解析成Expr类型,然后根据求导规则进行符号求导计算。最终实现控制台输入sin(2*x) 求导系统即可自动计算出(cos((2*x))*2)。

   在之前的博客中,我已经介绍了一下如何用F#对数学表达式进行求导,但是其中有一个问题,就是求导的对象是一个Expr类型,这样使用起来是很不方便的。一般来说,用户使用都是直接输入一个文本类型的数学公式,然后求导系统应该就可以对其进行自动解析成Expr类型,并根据求导规则进行求导的符号计算。

   如果对之前撰写的博文【F#表达式求导】,感兴趣的话可以自行去阅读。下面,给出利用F#解析器库FParsec实现公式求导符号计算。

1 文本解析到Expr

首先,在F#项目中引入FParsec库,并定义一个Expr类型:

typeExpr=|CstFoffloat|Varofstring|AddofExpr*Expr// +|SubofExpr*Expr// -|MulofExpr*Expr// *|DivofExpr*Expr// / |PowofExpr*Expr// ^ |SinofExpr|CosofExpr|ExpofExpr|LnofExpr|NegofExpr|FactorialofExpr|Errorofstring

       这个类型中包含了变量Var,数值CstF,加减乘除运算符,三角函数等。其次,再利用FParsec库提供的强大解析功能,给出一些解析工具方法。这些方法有的可以解析浮点类型,有的可以解析从小括号中提取表达式,具体示例如下:

//忽略空白字符letprivatews=CharParsers.spaces//忽略特定字符并忽略末尾的空白字符letprivatechc=CharParsers.skipCharc>>. ws//解析浮点类型的值,忽略末尾的空白字符,并转换成 CstF 类型letprivatenum=CharParsers.pfloat .>>ws|>> (funx->CstFx)
//变量标识符规则letprivateidentifier=letisIdentifierFirstCharc=isLetterc||c='_'letisIdentifierCharc=isLetterc||isDigitc||c='_'many1Satisfy2LisIdentifierFirstCharisIdentifierChar"identifier"//忽略空白letprivateidentifierws=spaces>>.  identifier  .>>spaces//变量解析,注意|>>与id对其,否则报错letprivateid=identifierws|>>(funx->Varx)
                   .>>ws//创建一个新的具有优先级的操作符解析器letprivateopp=newOperatorPrecedenceParser<_,_,_>()
//重命名表达式解析器ExpressionParser,方便调用letprivateexpr=opp.ExpressionParser//括号中提取表达式letprivatebra_expr=ch'('>>. expr .>>ch')'// 定义支持的术语terms,即操作符外的类型解析器//id表示变量解析器,num表示浮点类型解析器,bra_expr表示表达式解析器letprivateterms=choice[ id; num; bra_expr]
opp.TermParser<-termsopp.AddOperator(InfixOperator("+", ws,1, Associativity.Left, funxy->Add(x, y)))
opp.AddOperator(InfixOperator("-", ws,1, Associativity.Left, funxy->Sub(x, y)))
opp.AddOperator(InfixOperator("*", ws,2, Associativity.Left, funxy->Mul(x, y)))
opp.AddOperator(InfixOperator("/", ws,2, Associativity.Left, funxy->Div(x, y)))
opp.AddOperator(InfixOperator("^", ws,3, Associativity.Left, funxy->Pow(x, y)))
opp.AddOperator(PrefixOperator("sin", ws,4, true, funx->Sin(x)))
opp.AddOperator(PrefixOperator("cos", ws,4, true, funx->Cos(x)))
opp.AddOperator(PrefixOperator("exp", ws,4, true, funx->Exp(x)))
opp.AddOperator(PrefixOperator("ln", ws,4, true, funx->Ln(x)))
opp.AddOperator(PostfixOperator("!", ws,5, true, funx->Factorial(x)))
//忽略空白letprivateexpr_ws=ws>>. expr .>>ws//调用run方法调用字符解析器letprivateparses=CharParsers.runexpr_wss

     这里需要注意一下,不少方法定义的时候的有关键字private限定,这表示私有的,在模块内可以访问,但在模块外无法直接访问。下面再定义一个公有方法,可以在其他模块进行访问。具体示例如下:

letstrParsers=matchparseswith|Success(value, _, _)   ->value|Failure(err, _, _) ->Errorerr

    一般来说,一个函数返回的类型是一致的,由于parse解析文本后,返回的类型可以是成功的Success,也可以是失败的Failure,为了兼容考虑,这里定义了一个Error 类型,它也是一个Expr类型。而Success返回的value就是一个Expr类型。

2 Expr求导计算

     下面,给出Expr类型的求导函数,具体的示例如下:

letrecprivatediffe=matchewith|CstFf->CstF0.0|Varx->CstF1.0|Add(CstFa, Varx)   ->CstF1.0|Add(e1, e2)  ->Add(diffe1, diffe2)
|Sub(e1, e2)  ->Sub(diffe1, diffe2)
|Mul(CstFa, Varx) ->CstFa|Mul(e1, e2) ->Mul(diffe1, diffe2)
|Pow(Varx,CstFa) ->Mul(CstFa,Pow(Varx,CstF (a-1.)))
|Pow(e1,e2)  ->Mul(e2,Pow(e1, Sub(e2,CstF1.)))
|Sin(e1)  ->Mul(Cos(e1),diffe1)
|Cos(e1)  ->Mul(Neg(Sin(e1)),diffe1)
|Neg(e1)  ->Neg(diffe1)
|e->eletrecprivateprintExpre=matchewith|CstFf->stringf|Varx->x|Add(e1 , e2) ->"("+ (printExpre1) +"+"+ (printExpre2) +")"|Sub(e1 , e2) ->"("+ (printExpre1) +"-"+ (printExpre2) +")"|Mul(e1 , e2) ->"("+ (printExpre1) +"*"+ (printExpre2) +")"|Div(e1 , e2) ->"("+ (printExpre1) +"/"+ (printExpre2) +")"|Pow(e1 , e2) ->"("+ (printExpre1) +"^"+ (printExpre2) +")"|Sin(e1) ->"sin("+ (printExpre1) +")"|Cos(e1) ->"cos("+ (printExpre1) +")"|Neg(e1) ->"-("+ (printExpre1) +")"|_->failwith"printExpr error"letdiff1s=diff (strParsers) |>printExpr

  其中的diff是一个private内部方法,且用rec关键字限定说明是一个递归函数。这里需要注意一下,rec在private关键字之前,而不能调换位置。同理,printExpr函数则是将Expr类型的表达式输出为文本类型的数学公式。

    最后,定义一个diff1 函数,它接受1个文本类型的输入,首先通过strParser s 解析成Expr类型的对象,然后调用diff函数进行公式求导,并将求导后的结果作为参数传递给函数printExpr进行打印输出。

3 求导测试

     最后,给出求导公式的测试程序:

openSystemopenYd.ExpParser[<EntryPoint>]
letmainargv=Console.WriteLine"Welcome YdCAS Demo【JackWangCUMT】";
Console.WriteLine"目前只支持求导:sin(2*x) - > (cos((2*x))*2)";
Console.Write"$>";
letmutableinput=Console.ReadLine()
whileinput<>"quit"doprintfn"diff(%s) => %O"input (diff1input)
Console.Write"$>";
//input = Console.ReadLine() //不能修改初始值input<-Console.ReadLine() //可以printfn"%s"input0

  运行并输入如下测试用例,结果为:

WelcomeYdCASDemo【JackWangCUMT】目前只支持求导:sin(2*x) -> (cos((2*x))*2)
$>x^3diff(x^3) => (3*(x^2))
$>sin(x^2+3*x)
diff(sin(x^2+3*x)) => (cos(((x^2)+(3*x)))*((2*(x^1))+3))
$>x^2+3*xdiff(x^2+3*x) => ((2*(x^1))+3)
$>cos(x)
diff(cos(x)) => (-(sin(x))*1)
$>


相关文章
|
SQL 分布式计算 DataWorks
同步Hive表数据报block文件不存在问题 java.io.FileNotFoundException: File does not exist
同步Hive表数据报block文件不存在问题 java.io.FileNotFoundException: File does not exist
|
关系型数据库 块存储
ceph 故障分析(backfill_toofull)
在执行了 ceph 扩容之前, 发现长时间内都具有下面的状态存在 参考下面信息 # ceph -s cluster dc4f91c1-8792-4948-b68f-2fcea75f53b9 health HEALTH_WARN 13 pgs backfill_toofull; 1 pgs degraded; 1 pgs stuck degraded
7357 0
|
7月前
|
UED
销售易CRM:以用户体验为核心,驱动企业销售效能提升
销售易CRM是一款以用户体验为核心的企业客户关系管理工具。它通过简洁直观的操作界面降低学习成本,流畅稳定的系统性能提升办公效率,智能化功能助力精准识别高价值客户并优化销售流程,移动办公与离线支持打破时间和空间限制。全方位的高效、智能解决方案,助力企业在竞争中脱颖而出,实现持续发展。
|
9月前
|
存储 缓存 关系型数据库
社交软件红包技术解密(六):微信红包系统的存储层架构演进实践
微信红包本质是小额资金在用户帐户流转,有发、抢、拆三大步骤。在这个过程中对事务有高要求,所以订单最终要基于传统的RDBMS,这方面是它的强项,最终订单的存储使用互联网行业最通用的MySQL数据库。支持事务、成熟稳定,我们的团队在MySQL上有长期技术积累。但是传统数据库的扩展性有局限,需要通过架构解决。
198 18
|
数据安全/隐私保护 C++ Python
Base32系列编码 代码实现过程
Base32系列编码 代码实现过程
295 0
|
12月前
|
负载均衡 安全 网络安全
|
人工智能 自动驾驶 数据库
领域大模型的训练需要什么数据?
领域大模型的训练需要什么数据?
931 0
|
Android开发 iOS开发 开发者
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
1095 0
|
数据可视化 Java uml
精通UML:从类图到序列图的实战指南
【8月更文第23天】统一建模语言(Unified Modeling Language, UML)是一种用于软件工程的标准图形化语言,它提供了一套工具来帮助开发团队可视化、构造和文档化软件系统。在UML中,类图和序列图是最常用也是最重要的两种图。类图用于描述系统的静态结构,而序列图则用于表示对象之间的交互和系统的动态行为。
491 5
|
图形学
【unity知识点】Unity 协程/携程Coroutine
【unity知识点】Unity 协程/携程Coroutine
1010 0