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

相关文章

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序