欢迎光临
我们一直在努力

【Wolfram语言】47 写好代码

47 写好代码

词汇表

函数说明
FromDigits[list] 从数字列表组装整数
IntegerReverse[n] 反转整数的数字
Timing[expr] 计算并计时所用时间
Iconize[expr] 将表达式以图标形式显示

写好代码在许多方面就像写好散文:需要把想法弄清楚,并能清晰地表达。 刚开始写代码时,你大多会先用英语或其它自然语言来思考代码做什么。 但当你对 Wolfram 语言越熟练,你会开始直接用代码思考,输入程序往往比描述它更快。

Wolfram 语言的设计目标是尽可能让它表达事物变得容易。 Wolfram 语言中的函数类似于自然语言中的词汇,设计者也尽力为它们选择恰当的名字。

像 Table、NestList 或 FoldList 这样的函数存在于 Wolfram 语言中,是因为它们表达了人们常想做的事情。 和自然语言一样,总有许多方式可以表达同一件事。 但写好代码意味着找到最直接、最简单的表达方法。

平方数示例

良好代码

要创建前 10 个平方数的表,有一个显然的好代码,它直接使用 Table 函数。

简单且良好的 Wolfram 语言代码,用于生成前 10 个平方数的表:

In[]:=Table[n^2, {n, 10}]
Out[]={1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

糟糕代码

为什么有人会写别的方式? 一个常见问题是没有考虑“整个表”,而是只想到构建表的步骤。 在计算机早期,机器能力有限,别无选择,只能给出描述每一步的代码。

一个更糟糕的分步构建表的代码示例:

In[]:=Module[{list, i}, list = {};
For[i = 1, i <= 10, i++, list = Append[list, i^2]]; list]
Out[]={1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

但 Wolfram 语言的要点是让人以更高层次表达,并创建尽可能直接捕捉意图的代码。 了解语言之后,以这种层次编程会更高效,也更容易为计算机和人理解。

代码迭代

写好代码时,常常要问:“这段代码想做的总体是什么?” 你可能一开始只理解某部分,写出只针对该部分的代码,然后不断扩展。 但若考虑总体,你可能突然意识到有更强大的函数(比如 Fold)可以让代码再次简洁。

构建整数

制作将 {百位, 十位, 个位} 数字转为单个整数的代码:

In[]:=fromdigits[{h_, t_, o_}] := 100 h + 10 t + o

运行代码:

In[]:=fromdigits[{5, 6, 1}]
Out[]=561

推广

将其推广到任意长度的列表,使用 Table:

In[]:=fromdigits[list_List] :=
Total[Table[10^(Length[list] – i)*list[[i]], {i, Length[list]}]]

新代码工作正常:

In[]:=fromdigits[{5, 6, 1, 7, 8}]
Out[]=56178

简化

通过同时乘以整列 10 的幂来简化代码:

In[]:=fromdigits[list_List] :=
Total[10^Reverse[Range[Length[list]] – 1]*list]

尝试新方法

尝试不同的递归方法,先清除先前定义:

In[8]:=Clear[fromdigits]
In[9]:=fromdigits[{k_}] := k
In[10]:=fromdigits[{digits___, k_}] := 10*fromdigits[{digits}] + k

新方法也可行:

In[]:=fromdigits[{5, 6, 1, 7, 8}]
Out[]=56178

洞察过程本质

但随后你会意识到:其实这完全就是一个 Fold!

In[]:=Clear[fromdigits]
In[]:=fromdigits[list_] := Fold[10*#1 + #2 &, list]
In[]:=fromdigits[{5, 6, 1, 7, 8}]
Out[]=56178

使用内置函数

当然,也有内置函数可以完成相同任务:

In[]:=FromDigits[{5, 6, 1, 7, 8}]
Out[]=56178

代码简洁优点

为什么代码简洁好? 首先,因为更可能是正确的。 复杂代码更容易藏错。 简单代码通常更通用,能覆盖未预见的情况,减少额外代码。 最后,简单代码通常更易读和理解

短未必好

但“更短”未必更好,短小的“代码诗”有时难以理解。

一个过短但开始难以理解的 fromdigits 实现:

In[]:=fromdigits = Fold[{10, 1} . {##} &, #] & ;

它仍然可用:

In[]:=fromdigits[{5, 6, 1, 7, 8}]
Out[]=56178

拆分复杂代码

如果要做的事情本身复杂,代码可能不可避免地复杂。 良好代码应拆分为尽可能简单且自包含的函数和定义。 即便在很大的 Wolfram 语言程序中,单个定义往往也不会超过几行。

下面是一个结合了多个情况的单一定义:

In[]:=fib[n_] :=
If[! IntegerQ[n] || n < 1, "Error",
If[n == 1 || n == 2, 1, fib[n – 1] + fib[n – 2]]]

将其拆分为更简单的多个定义更好:

In[]:=fib[1] = fib[2] = 1;
In[]:=fib[n_Integer] := fib[n – 1] + fib[n – 2]

良好命名

为函数选择好名字是写好代码的重要方面。 对于 Wolfram 语言的内置函数,作者在几十年间做了大量工作去挑选恰当的名字,使短名字能传达函数的本质和使用方式。

当你写代码时,通常会为了某个特定上下文先定义一个新函数。 但通常值得给它一个在其他上下文同样能被理解的名字。 如果找不到好名字,通常说明这个函数不太适合定义。

一个好函数名的标志是:当在代码中看到它时,你能立即知道代码在做什么。 事实上,Wolfram 语言的一个重要特点是,优秀的代码通常比任何文字描述更易读和理解。

如何用白话来描述这点?

In[]:=Graphics[{White,
Riffle[NestList[Scale[Rotate[#, 0.1], 0.9] &,
Rectangle[], 40], {Pink, Yellow}]}]

请添加图片描述

抉择

当你编写 Wolfram 语言代码时,可能需要在使用单个稀有内置函数(恰好做你想要的事)与用几个常见函数组合实现相同功能之间做选择。 在本书中,我有时选择避免稀有函数以减少词汇量。 但最佳代码往往在可能时使用单个函数——因为函数名能解释代码意图,是单个片段无法替代的。

函数组合

用一小段代码反转整数的数字:

In[]:=FromDigits[Reverse[IntegerDigits[123456]]]
Out[]=654321

单一函数

使用单个内置函数能更清楚地表达意图:

In[]:=IntegerReverse[123456]
Out[]=654321

效率

好代码需要正确且易懂。 但也需要高效运行。 在 Wolfram 语言中,更简单的代码通常也更好,因为清楚的意图让语言更容易优化内部计算方式。

每个新版本中,Wolfram 语言在自动优化执行速度方面都有进步。 但你仍可通过良好构造算法来帮助它。

计时

Timing 给出计算的时间(秒)及其结果:

In[]:=Timing[fib[25]]
Out[]={0.0625, 75025}

可视化比较

画出根据之前定义计算 fib[n] 的时间。 使用上述 fib 定义时,时间增长很快:

In[]:=ListLinePlot[Table[First[Timing[fib[n]]], {n, 25}]]

请添加图片描述

算法优化

我们使用的算法在重算许多已计算值方面做了指数级的多余工作。 可以通过使 fib[n_] 的定义总是为 fib[n] 赋值,从而存储每个中间计算结果来避免这点。

将 fib 重新定义为记住它计算过的每个值:

In[]:=fib[1] = fib[2] = 1;
In[]:=fib[n_Integer] := fib[n] = fib[n – 1] + fib[n – 2]

现在即使到 1000,每个新值也只需微秒级时间计算:

In[]:=ListLinePlot[Table[First[Timing[fib[n]]], {n, 1000}]]

请添加图片描述

代码结构

好代码应有易读的结构。 但有时代码中需要包含一些细节,这些细节会妨碍看到主要要点。

图标化

在 Wolfram 笔记本中,有一个方便的方法:图标化这些细节(iconize)。

生成带有大量选项的绘图:

In[]:=ListLinePlot[Table[Prime[n]/n,{n,100}],Frame->True, PlotStyle->Orange,Filling->Axis,
FillingStyle->LightPurple,AspectRatio->1/3,Mesh->All]

请添加图片描述

将选项图标化后的相同代码:

在这里插入图片描述

在典型的 Wolfram 笔记本界面中,你可以选择想图标化的部分,使用右键菜单。 你会看到图标化形式,但底层仍是实际使用的代码。

当你想在笔记本中包含大量数据但不想显式显示时,图标化也很方便。

创建一千个素数的表,但仅以图标化形式显示:

In[]:=Iconize[Table[Prime[n], {n, 1000}]]

请添加图片描述

你可以复制图标化形式,然后在输入中使用它:

请添加图片描述

Out[]:=3682913

结语

写好 Wolfram 代码的要点总结:

  • 用Wolfram 语言直接思考,迭代优化代码;
  • 代码如散文,应优雅易理解;
  • 选择恰当的函数,优先使用单一内置函数,使用有意义的命名;
  • 拆分复杂逻辑,追求代码结构简洁与清晰;
  • 考虑算法效率。

下一步

调试代码

赞(0)
未经允许不得转载:171主机测评 » 【Wolfram语言】47 写好代码
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址