网教网

搜索
查看: 129|回复: 0

Mathematica上道——函数式编程用法举例

[复制链接]

1

主题

1

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-12-1 19:26:05 | 显示全部楼层 |阅读模式
宗旨:除了上界变化的情况外,能不用for就不用for,for mma is a Functional Programming Language

举个上界变化的例子:
x = 11;
For[i = 1, i <= x, ++i; Print, x -= 2; Print[x]]
x如果

这貌似是很经典的一个问题,虽然没什么说的,但是我还是另开了一个标题。
If[(*条件*),(*条件符合的话……*),(*否则*)] 如果你说我否则里面并没有啥要否则的,那么
a = 1
If[a < 0, a, Null]
映射

Map[f, {a, b, c, d, e}](*把函数f映射到一个数表中所有元素上*)循环,迭代,遍历条件

其实说到循环,函数可就太多了。但是不知道是甚么原因,“For”经常会被视作循环的同义词。其实与我们大家都很熟悉的语言——比如python——不同的是,在mma中,用For表示的循环通常都是低效且有很大程度上的繁杂性的——而且还很慢。
因为提升mma速度的方法之一就是把所有想计算的东西扔到kernel里面去——不要放到外面,正如for干的一样,他还会把数组拆散去挨个分析。
不说别的,先带大家看一下For最基本的用法。
In[13]:= list = {}; For[i = 2, i <= 10, i = i + 2,
AppendTo[list, Exp + i^2]]; list

Out[13]= {4 + E^2, 16 + E^4, 36 + E^6, 64 + E^8, 100 + E^10} 这里面就是for最基本的功能——生成一个列表。
构造表

list = {}; For[z = 7, z <= 8, z = z + 1/2,
For[y = 5, y <= 6, y = y + 2/10,
  For[x = 2, x <= 3, x = x + 1/10, AppendTo[list, {x, y, z}]]]];
Graphics3D[Point
    ]
    (*另外一种写法,Graphics3D@Point@list*) 举个例子,上面是在说[创建一个list,然后在z=7时开始,判断z=7,小于8,好下一个y=5开始,y小于6,下一个x=2开始,小于三,于是这三个xyz的赋值成功地被添加到了最后的表中。]
    当然,mma中还有其他类似函数:Array和Table。
    a = Array[# &, 100];
    Cases[_?EvenQ][a];
    F[x_] := E^x + x^2;
    F /@ %%
    (*或者*)
    Table[F[x], {x, 2, 100, 2}] Array正如其名,数组,指构造一组数,用的是匿名函数。当然现在看来,这两个函数还没有什么用。
    插一句,如何理解Table这个函数?
    Table[If[PrimeQ@i, i, Nothing], {i, 1000}]
    我来解释一下:
    Table把后面的那个参数也就是数表中的各个元素往前面扔,前面的一参数是个函数,这个元素经过了前面的函数的加工后被append到最终的生成表中。
    同样的目的也可以用if实现,不过没必要展开讲;用Range直接实现的话是这样子:
    Exp@# + #^2 &@Range[2, 100, 2]
    Exp@# + #^2 & // Range[2, 100, 2] 这两个乍一看还真差不多,不过一定要注意,后者的等效写法为Range[2, 100, 2]@Exp@# + #^2 &,并无意义!@是将前面的函数——这里面是隐函数,applyto后面的数组中!不能把数组作用在函数上(?听起来也怪怪的)
    循环


    • 部分内容借鉴自mma小吧无影冬瓜的回答。

    $$
    \sum i=\sum _{i=1}^{n} \sin (i)
    $$

    一个小技巧。在mma中打公式为了防止粘贴到别处显乱,可以先Ctrl+Shift+I。
    Sum[Sin, {i, 1, 10^6}] 作为一句废话,这当然是最简单的手段。
    但是与此同时不要忘了——咱们的题目可是《循环》啊。所以这个求和可以用循环表示:
    For[i = 1(*从1开始*), (*直到*)i <= 10^5, (*每运行一次*)i++(*就是i+=1的简写,i每运行一次都加1*), sum += Sin[N@i](*每运行一次sum加Sin[N@i],最后sum的赋值便是那个求和的结果*)] 很慢,不是嘛?你可以试试用Module封装完了(后面会讲)、AbsoluteTiming之后看看时间,比如说:
    Do[a[n] =
       AbsoluteTiming[
        Module[{sum = 0, i}, For[i = 1, i <= 10^5, i++, sum += Sin[N@i]];
         sum]], {n, 1, 10}];
    Do[b[n] = a[n][[1]], {n, 1, 10}];
    Table[b[n], {n, 1, 10}];
    Mean@% 这时候你就会意识到Do的最基本用法——Do[Option,n次],通常来讲Do均比For快。
    如果想更快一点,可以:
    Tr@Sin[N@Range[10^6]] 这里面tr是广义迹,也就是将后面的一串sini来看作一组 向量在各维的投影,求广义迹便是求元素和,也即向量化运算——这种运算方式的优越性在后面也会有提到。当然了,Add肯定比tr快,毕竟人家是专门干这个的。
    Module[{}, Add@{1, 2, 31, 3^12341234}] // AbsoluteTiming直到……

    上面刚说完Do这个函数,那么有的人就会对这个函数的普适性提出质疑了:
    Do函数,仅仅执行不能判断。但是For就很好。比如一段循环,满足精度就停止。这时用Do,需要添加别的判断语句了
    那么这种情况自然Do麻烦了,但是不代表没有其他的函数能怼上。函数式编程,讲究的就是个各有所长。

    迭代

    接下来说说迭代。
    举个例子:
    x = 0.5;
    For[i = 1, i <= 10^6, i++,
    x = 3.5 x (1 - x);{}];
    x 想要快一点点就别都加For,试试Nest:
    Nest[3.5 # (1 - #) &, 0.5, 10^6] nest也不难理解,就是声明一个纯函数再把0.5带进去,计算出结果,再把该结果带进去,出下一个结果,像这样重复10^6次——不过这样就会更快了。
    遍历条件、筛选

    再说说遍历列表求偶数个数。
    前面咱们已经说过了,规则可以用f[x_?EvenQ] := 3x;(*EvenQ是规则,判断是否为偶数*)类似的“?+条件”来表明。那么下面也一样。
    (*前设list为一10^6长度的随机数表*)
    num = 0;
    For[i = 1, i <= 10^6, i++,
    If[EvenQ@list[], num++]
    ];
    num 很好理解。那么比For更正宗的,是Scan:
    num = 0;
    Scan[If[EvenQ@#, num++] &, list];
    num 或者:
    Count[list, _?EvenQ] // AbsoluteTiming
    (*直接用Count数出list中偶数的个数*)
    Count[EvenQ /@ list, True] // AbsoluteTiming
    (*用Map(/@)把判断偶数的规则映射到list上,然后用Count数出结果中True的个数*)
    Select[list, EvenQ] // Length // AbsoluteTiming
    (*选取list中的所有偶数,然后求结果列表长度*)
    Count[EvenQ@list, True] // AbsoluteTiming
    (*利用EvenQ的Listable属性直接判断list的每个数是否偶数,然后数出结果中True的个数*)
    (*原网页链接:https://www.zhihu.com/question/34573590/answer/59844631 一般看哪个快主要看参数的数量、用途的专一性。不过作为一个看起来不太专一的例子,下面的代码其实是最快的:
    Sum[Boole[EvenQ@i], {i, list}]
    (*i取1、2、3,求布尔值的和——当i为偶数时布尔值为1*) 还有下面的Table通法:
    Table[If[EvenQ@i, i, Nothing], list] // Length 前面已经提到过了,不再赘述。
    在一部分例子中,筛选的本质是模式匹配,我就在模式匹配那一章中再做展开吧。
    代码整理工具——Module

    Module其实不难理解,就是把程序划分成一块一块以增加可读性、免去重复管理变量赋值的一种快捷手段。Module[{x,y},expr]的意思是把xy看作局部变量,非全局变量,出了这块地方xy就恢复成原来的未赋值状态了;Module[{x=0},expr]的意思是给出局部的初值。一般在计算过程所需时间时都需要用AbsoluteTiming搭配module使用。
    Do[a[n] =
       AbsoluteTiming[
        Module[{sum = 0, i}, For[i = 1, i <= 10^5, i++, sum += Sin[N@i]];
         sum]], {n, 1, 10}];
    Do[b[n] = a[n][[1]], {n, 1, 10}];
    Table[b[n], {n, 1, 10}];
    Mean@% 如果你非得想要把每次的被包起来的结果输出的时候,可以用print函数,F1自行查看用法。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表