5、过程式编程和调试技巧 返回目录页
一、循环
二、判断
三、模块化
四、循环+判断
五、调试技巧
MMA支持全部标准的过程式编程(Procedural programming)结构,但每每经过集成把它们扩展到更为广泛的符号编程环境中。
-------------------------------------------------------------------------
一、循环
MMA支持多种循环,经常使用的是Do循环与While循环。
----------------------------------------
特殊形式的赋值
i++ i 加 1
i-- i 减 1
++i 先给 i 加 1
--i 先给 i 减 1
i+=di i 加 di
i-=di i 减 di
x*=c x 乘以 c
x/=c x 除以 c
x=y=value 对 x 和 y 赋同一值
{x,y}={value,value} 对 x 和 y 赋不一样的值
{x,y}={y,x} 交换 x 和 y 的值
----------------------------------------
迭代器。
没啥新鲜的,之前在用Table函数时,就已经用到过。
最后一行指控制多重循环。
----------------------------------------
语句块
由于在MMA中,只有表达式(惟一的例外是序列,但序列不能单独使用),因此语句块,也是表达式块。
语句块的做用,是将多个语句组合起来,而造成一个语句(表达式)。
在MMA中,没有专用的语句块符号,如Pascal语言中的Begin/End,如C语言中的{}。
若是实在要用,就用()。并且有时候是必须的,而有时候又不是必须的。
test[n_, i_] := (t = 2^n; Plus[t, i])
test[2, 2]
前一行中,()是必须的。()内两个语句,第一句运行没输出。第二句运行也没输出,但计算结果做为函数的返回值返回了——从而在函数被调用的时候,产生输出。
Do[
(Print[a];
Print[b];
Print[c];
Print[d]),
{1}]
这个()是多余的,不是必须。用上与没用上效果同样。
但用上了以后,代码的可读性彷佛增长了。
语句块是支持嵌套的:
(((a; b; c); (d; e)); (f; g))
但不经常使用,基本上见不到这种用法。(却是让人想到了Lisp语言家族的表达式)
----------------------------------------
Do 循环。
Do循环的构成很是简洁:
Do[expr,迭代器]
Do[Print[你好], {2}]
Table[你好, {2}]
在语法上,Do函数与Table很像。Table函数创造出表,Do函数运行表达式数次。
expr部分,能够是单条语句,也能够是语句块。
Do[Print[你好吗]; Print[很好], {2}]
这个语句块中的()不是必须,但若是用上也行。
Do[(Print[你好吗]; Print[很好]), {2}]
由于步长能够是负值,实现“Down to”就很容易。
Do[Print[i], {i, 10, 8, -1}]
迭代器其实是很灵活的,不必定用整数,能够用符号值。
Do[Print[n], {n, x, x + 3 y, y}]
实际多重循环,也是易如反掌:
Do[Print[{i, j}], {i, 3}, {j, i - 1}]
在MMA中,循环又叫迭代。
t = x; Do[t = 1/(1 + t), {5}]; t
Nest[1/(1 + #) &, x, 5]
这两行语句的效果是同样的。
Do[Print[RandomInteger[{1, 30}]], {10}]
输出10个1到30以内的伪随机数。
其实有内置函数来作这事:
RandomSample[Range[30], 20]
内置函数不熟悉的话,就是比较吃亏。
要注意的一点是,Do函数自己没有返回值(即返回Null)。
Null
是一个符号,用来指明一个表达式或结果不存在。 在普通输出中它不显示。
当 Null 显示为一个彻底输出表达式时,没有任何输出显示。
Null
得:无输出。
因此为了观察循环体内的运算过程,咱们用了不少Print输出。
----------------------------------------
While 循环。
基本格式也极其简洁:
While[test,body]
重复计算 test,而后是 body,直到 test 第一次不能给出 True。
body部分,能够是单条语句,也能够是语句块。语句块用不用(),都可。
n = 1; While[n < 3, Print[n]; n++; Print[hello]]
n = 1; While[n < 3, (Print[n]; n++; Print[hello];)]
最后一个分号,写不写上都可。
n = 1; While[n < 4, Print[n]; n++]
写成如下这样,效果都同样:
n = 1; While[n < 4, (Print[n]; n++;)]
n = 1; While[n < 4, n++; Print[n - 1];]
n = 1; While[n++ < 4, Print[n - 1]]
{a, b} = {27, 6};
While[b != 0, {a, b} = {b, Mod[a, b]}];
a
展转相除法求最大公约数。
相似的方法,能够求出一系列的Fib数。
{a, b} = {0, 1}; n = 0;
While[n++ < 10, {a, b} = {b, a + b}; Print[b]]
Break 退出 While:
{a, b} = {0, 1}; n = 0;
While[True, n++; If[n > 10, Break[]]; {a, b} = {b, a + b}; Print[b]]
While 返回的最后结果是 Null。
-------------------------------------------------------------------------
二、判断
判断指一些条件函数(conditional function),依据条件(condition)的不一样返回值(True/False/不肯定)而返回不一样表达式的值。
----------------------------------------
IF 判断。
If[condition,t]
若是 condition 计算为 True 给出 t,若是它计算为 False,则给出 Null。
If[condition,t,f]
若是 condition 计算为 True 给出 t,若是它计算为 False 给出 f。
If[condition,t,f,u]
若是 condition 计算既不为 True 也不为 False 给出 u。
奇怪哈,竟然条件计算结果有第三种可能?
这就是符号计算的特点了。看:
a =.; b =.;
If[a == b, t, f, u]
得u
由于a、b只是符号,还不知道是啥值,因此a与b是否是相等?不知道。
condition部分,能够是语句块。()不是必须。
If[(a = 1; b = 2; a == b), t, f, u]
If[a = 1; b = 2; a == b, t, f, u]
分号、逗号一大堆,加上清楚好多。
固然,瓜熟蒂落,t/f/u部分,均可以是语句块。
a =.; b =.;
If[a == b, t, f]
得:If[a == b, t, f]
若是 condition 计算既不为 True 也不为 False,If[condition,t,f] 保持不计算。
abs[x_] := If[x < 0, -x, x]
abs /@ {-1, 0, 1}
If[TrueQ[2 < 4], 1, 0]
得:1
If[TrueQ[2 < 1], 1]
得:Null(无输出)
If支持嵌套。
若是x为1,得a,x为2,得b,x为3,得c,x为其余,得other。用If嵌套这样写:
x = 20;
If[x == 1, a,
If[x == 2, b,
If[x == 3, c, other]]]
其实这样写也能够。但用如下函数Which更好。
----------------------------------------
Which
看程序就很明白了。
x=2;
Which[x==1,a,x==2,b,x==3,c,True,other]
----------------------------------------
Switch
同样,看程序:
Switch[x, 1, a, 2, b, 3, c, _, other]
-------------------------------------------------------------------------
三、模块化
MMA通常假设变量是全局变量。即每次使用 x 等名字时,MMA总认为在调用同一对象。
然而在编程时,不须要将全部变量都做为全局变量。不少时候,咱们但愿用局部变量,好比在自定义函数体内。
在MMA中,能够用Module函数定义局部变量,能够用With函数定义局部常量。
----------------------------------------
Module
Module[{x,y,...},body] 具备局部变量 x,y,...的模块
t = 1;
Module[{t=t+2}, t=t+2; Print[t]];
t
这个程序很能说明问题了。
全局变量t,能够到Module的{}中去,而局部变量t是仅模块内可用的。
仔细观察能够看到,两个t的颜色是不一样的。
如图,在“偏好设置”中,把三种变量设置成不一样的颜色,是很重要的。html
Module函数是有返回值的,即body表达式的返回值。
t = 1;
x = Module[{t = t + 2}, (t = t + 2; Print[t]; t + 2)];
t
x
body能够是语句块,这个语句块能够加上(),也能够不加。
----------------------------------------
With
在 Module 中能够定义局部变量,这样咱们能够对其赋值而且改变其值。
然而,一般咱们所须要的是局部常量,对其咱们仅须要赋值一次。
With 结构能够创建局部常量。
With[{x=x0,y=y0...},body] 定义局部常量 x,y,...
这样是不能够的,由于局部常量不能够在body中再改变。
t = 1;
With[{t = t + 2}, t = t + 2; Print[t]];
t
这样就能够了:
t = 1;
With[{t = t + 2 + 2}, Print[t]];
t
能够将 With 理解为 /. 运算的推广:
t = 1;
t /. t -> t + 2 + 2
t
With 相似于局部变量仅赋值一次的 Module 的特殊形式。
With函数有返回值,即返回body表达式的输出值。
t = 1;
x = With[{t = t + 2 + 2}, Print[t]; t + 2];
t
x
还有个模块函数叫块(Block)。由于Block与Module有细微差异,这里不展开,有兴趣的小朋友能够研究帮助文档。
-------------------------------------------------------------------------
四、循环+判断
过程式编程,用循环+判断,能够解决不少问题。
若是要自定义函数,则要结合模块。
这里举几个栗子。
----------------------------------------
fib[n_] := Module[{f},
f[1] = f[2] = 1;
f[i_] := f[i] = f[i - 1] + f[i - 2];
f[n]
]
fib[5]
----------------------------------------
对最大公约数(GCD)使用初始化局部变量的欧几里德(Euclid)算法。
一般叫展转相除法。
gcd[m0_, n0_] := Module[{m = m0, n = n0},
While[n != 0, {m, n} = {n, Mod[m, n]}];
m
]
gcd[120, 48]
----------------------------------------
筛法求素数。
primes2[n_] :=
Module[{primes = Range[n], p = 2},
Print["Start: primes = ", primes, ", p=", p];
While[p != n + 1, Do[primes[[i]] = 1, {i, 2 p, n, p}];
p = p + 1;
While[p != n + 1 && primes[[p]] == 1, p = p + 1];
Print["In loop: primes=", primes, ", p= ", p];];
Select[primes, (# != 1) &]]
primes2[12]
这个程序输出了中间过程,帮助理解筛法的思路。
如下程序加了中文注释,是不能够运行的,只能用于理解:
primes2[n_] :=
Module[{primes = Range[n], p = 2},
Print["Start: primes = ", primes, ", p=", p]; 输出初始表,及p值
While[p != n + 1,
Do[primes[[i]] = 1, {i, 2 p, n, p}]; 置1是在这里进行的,i从2p到n,步长为p
p = p + 1;
While[p != n + 1 && primes[[p]] == 1, p = p + 1]; 若是越界,或者p位置上已是1,跳过,再p+1
Print["In loop: primes=", primes, ", p= ", p]; 输出循环时的表及p值
];
Select[primes, (# != 1) &]] 将置1的值从表中删除,剩下的就是素数
primes2[12]
把中间过程去掉:
primes[n_] :=
Module[{primes = Range[n], p = 2},
While[p != n + 1, Do[primes[[i]] = 1, {i, 2 p, n, p}];
p = p + 1;
While[p != n + 1 && primes[[p]] == 1, p = p + 1];];
Select[primes, (# != 1) &]]
primes[100]
这里有两个primes,名称相同,意义彻底不一样(一个是全局变量、另外一个是局部变量)。在程序中,显示的颜色也不一样。算法
若是用内置函数,一句就行:编程
Prime[Range[25]]dom
-------------------------------------------------------------------------
五、调试技巧
MMA中,有自带的调试器,菜单中:计算/调试,就能够打开调试器。这里不展开。
这里说一些小技巧。
A、若是发现莫名其妙的结果,多数是某个名称在前面被取绰号了,赶忙用Clear试试。
B、若是函数名记不全,有自动完成功能,很好用,能够先输入一两个字母(注意,大小写敏感的)。
C、多按F1,查帮助文档。MMA的帮助文档,多是史上最强大的。
D、表达式的计算过程很难理解的时候,或者表达式很长的时候,用绰号来拆分。
E、用Trace函数来跟踪输出。
F、在程序中加入Abort函数来终止程序执行。加入到不一样部分,能够分步调试。
G、很重要的一点是,用Print函数来输出中间结果,从而观察中间结果。输出中间结果是万精油,各类语言均可以经过此大法来调试。
H、长时间调试程序容易疲倦。疲倦时就休息,坚持下去常常效率并不高。
I、程序长时间运行,得不出结果,极可能进入死循环了,按 Alt+. 终止程序执行。
J、MMA是很庞大的系统,短期内不精通不用心急。先掌握简单的。
K、越扯越远。。打住
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23:44 2016-10-29模块化
+++++++++++++++++++++++++++函数
扩展阅读:为何在 Mathematica 中使用循环是低效的?
oop
T o p
spa