mathematica-基于模式编程

2024-01-29 07:18
文章标签 模式 编程 mathematica

本文主要是介绍mathematica-基于模式编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

到目前为止,我们学习的编程范例都可以归类为命令式编程,程序员的工作就是一步一步地说明如何执行问题的解决方案。基于规则的编程与此完全不同。在基于规则的范例中,程序员只需写下一组规则,指定在解决问题的过程中遇到的任何表达式应该应用什么转换。程序员不需要指定执行这些规则的顺序;底层编程系统会指出这一点。(看着有点像IOC容器)。

基于规则的编程是实现数学计算的一种非常自然的方式,因为符号数学本质上是将转换规则应用到表达式(例如,微分规则,积分表)。Mathematica的多用途模式匹配能力,我们才刚刚开始探索,往往使基于规则的编程成为Mathematica程序员的选择范例。

模式

什么是模式

模式是一个Mathematica表达式,它表示整个表达式类。模式最简单的例子是一个Blank,_,它表示任何表达式。另一个例子是_f,它表示任何以f为头的表达式。我们已经在函数定义中使用了这样的模式作为形式参数。

模式本身是表达式,几乎可以为模式的任何部分提供临时名称,这允许使用规则提取和操作表达式的各个部分。这些临时名称称为模式变量。这里有三个例子:

In[2]:= (f[a]+g[b])/f[a,b]/.x_f:>x^2
Out[2]= (f[a]^2+g[b])/f[a,b]^2
In[3]:= (f[a]+g[b])/f[a,b]/.f[x_]:>x^2
Out[3]= (a^2+g[b])/f[a,b]
In[4]:= (f[a]+g[b])/f[a,b]/.f[x_,y_]:>(x+y)^2
Out[4]= (f[a]+g[b])/(a+b)^2

解构

下面演示从一个表达式中,拿出感兴趣的部分(匹配子表达式是模式非常重要的功能,能快速定位子表达式):

In[5]:= f[a]+g[b]/.x_[_]:>x
Out[5]= f+g
In[6]:= f[a]+g[b]/.x_[_,_]:>x
Out[6]= Plus
In[7]:= f[a]+g[b]/.x_[y_]:>y[x]
Out[7]= a[f]+b[g]

实际上,解构和规则替代通常比使用MapAt更容易修改子表达式, 它几乎总是更容易一眼理解destructuring操作做什么, 而不是什么一个表达式的一部分被称为长序列的下标。惟一需要使用下标而不是结构的情况是,一个表达式包含多个具有完全相同结构的子表达式,而您只希望提取或修改其中一个子表达式。

下面把一些列的点对转换成logplot适配的形式。

基于函数编程的方法1:

data={{x1,y1},{x2,y2},{x3,y3},{x4,y4}};
In[102]:= Transpose[data]
Out[102]= {{x1,x2,x3,x4},{y1,y2,y3,y4}}
In[103]:= MapAt[Log,%,{2}]
Out[103]= {{x1,x2,x3,x4},{Log[y1],Log[y2],Log[y3],Log[y4]}}
In[104]:= Transpose[%]
Out[104]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

基于函数编程的方法2:

In[105]:= MapAt[Log,data,{All,2}]
Out[105]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

基于模式编程的方法(一次规则替换即可):

In[106]:= data/.{x_,y_}:>{x,Log[y]}
Out[106]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

测试模式

有两个方法可以测试模式:MatchQ和Cases。

MatchQ

测试一个表达式是否匹配给定的模式。

In[107]:= MatchQ[a+b+c,_Plus]
Out[107]= True

Cases

测试一个表达式(不仅仅是列表,mathematica分为表达式和子表达式)中匹配模式的所有子表达式,并且能够进行规则替换,返回最终的结果。

In[109]:= Cases[{a,a+b,a+a},x_+y_]
Out[109]= {a+b}
In[110]:= Cases[{a,a+b,a+a},x_+y_:>y]
Out[110]= {b}
In[111]:= Cases[5 (a+b)^6,_Integer]
Out[111]= {5}

还能够指定层级:

In[112]:= Cases[5 (a+b)^6,_Integer, Infinity]
Out[112]= {5,6}
In[113]:= Cases[5 (a+b)^6,_Symbol, Infinity]
Out[113]= {a,b}

也能显示头部(头部也是符号):

In[114]:= Cases[5 (a+b)^6,_Symbol, Infinity,Heads->True]
Out[114]= {Times,Power,Plus,a,b}

属性的作用

Mathematica非常善于解构。考虑下面的例子:

In[118]:= Length/@{a+b+c,x_+y_}
Out[118]= {3,2}
In[119]:= a+b+c/. x_+y_:>{x,y}
Out[119]= {a,b+c}

为什么会这样匹配呢?因为Mathematica知道Plus是一个结合运算子,比如a+b+c==a+(b+c)。这些信息被编入Plus函数的属性中:

In[120]:= Attributes[Plus]
Out[120]= {Flat,Listable,NumericFunction,OneIdentity,Orderless,Protected}

Listable表示函数可以自动作用于列表。
Flat表示函数是可结合的。
OneIdentity表示Plus[x]==x。
Orderless表示Plus是可交换的,如,Plus[a,b]==Plus[b,a]。
Protected表示不能给Plus定义新的规则,除非先Unprotect它。

In[122]:= Cases[{a+b*c,a*b+c},x_+y_*z_]
Out[122]= {a+b c,a b+c}

这种行为允许一些相当复杂的转换,并且只需要很少的工作。例如,下面的规则将展开任意因子的乘积,只要其中任何因子至少包含两个附加项:

expandrule=x_(y_+z_):>x y+x z;
In[124]:= a(b+c)(d+e+f)/.expandrule
Out[124]= a b (d+e+f)+a c (d+e+f)
In[125]:= %/.expandrule
Out[125]= a b d+a c d+a b (e+f)+a c (e+f)
In[126]:= %/.expandrule
Out[126]= a b d+a c d+a b e+a c e+a b f+a c f

先使用基于过程编程的迭代方式(注意三个等号):

In[4]:= Module[{init=a(b+c)(d+e+f),last},While[True,last=init/. expandrule;If[last===init,Break[],init=last]];last]
Out[4]= a b d+a c d+a b e+a c e+a b f+a c f

可以使用基于函数编程的迭代方式:

In[127]:= FixedPoint[#/.expandrule&,a(b+c)(d+e+f)]
Out[127]= a b d+a c d+a b e+a c e+a b f+a c f

也可以使用基于模式编程的迭代方式:

In[128]:= a (b+c)(d+e+f)//.expandrule
Out[128]= a b d+a c d+a b e+a c e+a b f+a c f

使用模式的函数

Count:计算表达式中匹配模式的子表达式的个数。
Position:计算表达式中匹配模式的子表达式的位置。
DeleteCases:删除表达式中匹配模式的子表达式,可以指定层级。

规则和函数

在本节中,我们将看到规则和函数之间有着密切的联系。然而,在继续之前,我们需要引入一种新类型的规则。

延迟规则

正如有两种类型的赋值操作Set和SetDelayed一样,也有两种类型的规则:Rule和RuleDelayed。这两种形式的区别在于,直到模式变量被替换之后,才会对RuleDelayed的右边进行计算。可以使用语法a:> b指定延迟规则。

expr=Sqrt[u-1]/Sqrt[u^2-1];
In[20]:= expr/.u^2-1->(u-1)(u+1)
Out[20]= Sqrt[-1+u]/Sqrt[(-1+u) (1+u)]

这个规则在这个简单的情况下是适用的,但在一个更复杂的情况下,它会成为一个负担,必须提供多项式的因式形式。在这种情况下,我们可以尝试以下形式的规则。

In[22]:= expr/. 1/Sqrt[y_]->1/Sqrt[Factor[y]]
Out[22]= Sqrt[-1+u]/Sqrt[-1+u^2]

问题是,Factor[y]立即计算为普通的y,所以规则的作用是用y替换y_完全没有变化!防止在替换之前对规则的右边进行计算是这类问题的解决方案。

In[23]:= expr/. 1/Sqrt[y_]:>1/Sqrt[Factor[y]]
Out[23]= Sqrt[-1+u]/Sqrt[(-1+u) (1+u)]

函数定义是规则

当你在Mathematica中定义一个函数时,你实际上是在定义一个规则。函数f的规则可以使用DownValues[f]来显示:

In[27]:= fact[n_Integer]:=Times@@Range[n]
DownValues[fact]
Out[28]= {HoldPattern[fact[n_Integer]]:>Times@@Range[n]}

HoldPattern类似Hold,保持参数不被计算,不同于Hold的是,HoldPattern用于模式识别的忽略。

函数创建的规则和一般的规则不同的地方是,前者是全局作用的,后者只在Replace类型操作符(/.)中有效。内核匹配一个表达式到一个规则时,为替换为规则的右边。可以把内核执行表达式看成是:

expression //. (all global rules)

换句话说,全局规则持续被应用直到表达式停止改变。也正是由于这个原因Mathematica内核主执行程序被成为无限执行系统。这也解释了为什么只执行表达式到一半如此地难。

根据Roman Maeder,一个Mathematica编程语言的设计者之一,模式匹配和规则替换是Mathematica实现所有其他编程结构的底层机制。这个事实,和把所有东西都统一表示为表达式,赋予了Mathematica强大的能力和灵活性。

同一个符号的多个定义

Mathematica允许为同一个符号书写多个函数定义,来覆盖(大概)不同的情况。下面是fact的递归定义:

Clear[fact]
fact[0]=1;
fact[n_Integer]:=n*fact[n-1]
In[6]:= DownValues[fact]
Out[6]= {HoldPattern[fact[0]]:>1,HoldPattern[fact[n_Integer]]:>n fact[n-1]}

同一个函数有多个定义类似C++语言的重载,但事实上更加强大。最明显的一点,Mathematica模式可以测试不仅仅是一个参数的“类型”。

不太明显,或许更重要的是,不同的规则不必不相交(可以有交集):注意到0是整数,但当参数是0时,规则fact[0]被使用而不是fact[_Integer]。这阐明Mathematica模式匹配引擎的一般策略:总是在更一般的规则之前尝试更具体的规则。DownValues[f]展示内核尝试作用的f的有序规则。也可以使用?sym查看一个符号所有规则的顺序。

当哪个规则更具体不明显时,Mathematica保持它们录入时的顺序。在fact例子中,内部的规则是相同的,无论哪个规则先录入,因为模式0比模式_Integer更加具体。其他的情况Mathematica可以不会总是有能力指出规则的正确顺序。这些情况,需要显示重排一个符号的DownValues。

最后,可以使用=(Set)或:=(SetDelayed)来定义fact的基本情况,因为右边是一个常数。

基于模式的函数定义的优势

把一个函数实现为一个规则的而不是一整个代码块有很多优势。

首先,基于模式函数通常比都在一个函数的定义更加快,因为大部分情况下Mathematica的模式匹配引擎执行规则集比显示条件声明(比如,If,Switch)更加快速。

其次,使用多个规则定义函数对数学家来说非常自然。比如,下面是绝对值函数的定义,在笔记本中的呈现是平行的:

absval[x_]:=x /;x>=0
absval[x_]:=-x /;x<0

再次,很多数学简单地涉及根据识别的模式重写表达式(比如,微分和积分)。手动做数学简化时,其实在脑中做模式识别。这样,基于模式编程经常产生关于数学问题的清晰,优雅的方案。本书的剩余部分将会看到关于这些的例子。

第四,从算法的角度,不需要限制我们的想法把全局规则看成函数。创建f[constant]任意定义的能力允许我们把全局规则想成一种存储知识的方式——基于规则。标准包MiscellaneousCityData和MiscellaneousChemicalElements是以这种方式考虑全局规则的极好例子。

第五,一个Mathematica函数在执行时可以增加它自己的规则,允许耗时的计算被缓存。

选择性清空定义

Clear[f]清空所有定义,从头再来。可以使用“f[patt]=.”选择性地清空一个定义。这个操作被称为Unset。

fact[n_]=.

也可以通过赋值到DownValues[f]直接修改函数地DownValues。比如:

DownValues[f]:=Drop[DownValues[f],{i}]

“纯”基于函数编程

除了把规则写成函数定义,也可以本地地使用ReplaceRepeated把规则应用到表达式,本质上模仿内核的操作。比如,这里是另外一种方法计算阶乘。注意必须按如下顺序指定规则,否则f[0]永远不会被使用。

In[21]:= f[5]//. {f[0]:>1,f[n_]:>n*f[n-1]}
Out[21]= 120

与递归相比,这个技术不构造一个大的执行栈,所以不受$RecursionLimit“安全保障”影响。

ReplaceRepeated和它地变体顺序地扫描用来模式匹配地规则列表。对于长的规则列表,Replace-类的操作可以使用Dispatch函数来为规则表生成一个dispatch table来提高速度。

这篇关于mathematica-基于模式编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/656144

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言