摘要
Sow
和 Reap
提供了一种可以快速高效的存储方法,可以高效地存储计算过程中产生的中间结果。这一方法在效率上优于 AppendTo
。
1. 引言
设想一个场景,如果我们需要从一个混有苹果、梨、枣的篮子里将苹果挑选出来,最自然的想法是找一个新的篮子,从原有的篮子中随机拿出一个来,检测是不是苹果,是的话放入新的篮子之中。仿照这一想法,我们来提取出 10000 以内的所有素数,最直观的方式是创建一个空的数组,用于存放挑选出来的素数:
s={};
然后从 1 到 10000 逐个检验,如果是素数,则将它加入上面创建的 s
数组:
If[PrimeQ[#], AppendTo[s, #]]&/@Range[10000]
这种方式的思路很好理解,但是对于计算机来说,效率不会很高。事实上,我们在 Mathematica 程序中要尽量避免使用 AppendTo
。从 Mathematica 的哲学出发,应该使用 Reap
和 Sow
来达成相关需求 [1]。
2. 基本用法
从字面意思上来理解,Sow
是播种,Reap
是收获。它们一起组合使用,提供方便的途径存储在计算中产生的中间结果 [2][3][4]。在使用中,可以这样理解它们的工作过程:计算 Reap
内的表达式,若遇到 Sow
,则标记它的地址,最后将所有标记的项附在外层方括号内(除 Sow
)表达式的结果之后输出(可以这么理解,但程序内部的实现未必如此)[1]。以上面的那个问题为例,使用 Reap
和 Sow
的话,代码应该写为:
Reap[If[PrimeQ[#], Sow[#]]&/@Range[10000]]
不过需要注意的是,这段代码运行结束,得到的不止是 10000 以内的素数表,还包括最外层方括号内表达式的运行结果,我们只需在最后加上 [[2,1]]
即可得到与引言中一致的结果。
3. 并行计算
在计算规模很大的时候,使用并行化的程序可以极大地缩短运行时间。不过 Reap
和 Sow
本身不能自动用于并行化计算。例如上一个基本用法中给出的代码改为如下的并行化映射之后不能正常工作:
Reap[ParallelMap[If[PrimeQ@#, Sow@#]&, Range[10000]]]
为了能够使用并行计算加速程序,我们可以使用 ParallelCombine
将计算分配到所有并行内核上。具体的操作如下,首先定义一个可以映射于 list
,并执行 Reap
和 Sow
的函数[5][6]:
fun = Function[list, Reap[Map[If[PrimeQ@#, Sow@#] &, list]]]
显然,如果不要求程序并行化,只需执行 fun[Range[10000]]
,这与基本用法中给出的表达式一致的。而并行化版本为:
ParallelCombine[fun, Range[10000]]
除此以外,还可以使用 SetSharedFunction
来设置共享函数[5][6]。不过我觉得这种方式不太美观。
4. 总结
没啥好总结的!
![](https://img.haomeiwen.com/i6747808/81098927fcc18f34.png)
参考文献
-
Mangano S. Mathematica Cookbook: Building Blocks for Science, Engineering, Finance, Music, and More. " O'Reilly Media, Inc.", 2010: p57-58. ↩ ↩
-
https://reference.wolfram.com/language/tutorial/CollectingExpressionsDuringEvaluation.html ↩
-
https://mathematica.stackexchange.com/questions/33395/parallelmap-and-sow-reap-not-behaving-as-expected ↩ ↩
-
http://blog.wolfram.com/2011/04/20/mathematica-qa-sow-reap-and-parallel-programming/ ↩ ↩
网友评论