假设我们要生成一个素数列表 p ,其中 p + 2也是素数。
快速解决方案是生成第一个 n 素数的完整列表,并使用Select函数返回满足条件的元素。
Select[Table[Prime[k], {k, n}], PrimeQ[# + 2] &]
然而,这是低效的,因为它在返回过滤列表之前将大型列表加载到内存中。具有Sow / Reap(或l = {}; AppendTo[l, k]
)的For循环解决了内存问题,但它远非优雅,并且在Mathematica脚本中实现多次很麻烦。
Reap[
For[k = 1, k <= n, k++,
p = Prime[k];
If[PrimeQ[p + 2], Sow[p]]
]
][[-1, 1]]
理想的解决方案是内置函数,允许使用类似的选项。
Table[Prime[k], {k, n}, AddIf -> PrimeQ[# + 2] &]
答案 0 :(得分:18)
我将更多地解释为关于自动化和软件工程的问题,而不是关于手头的具体问题,并且已经发布了大量解决方案。 Reap
和Sow
是收集中间结果的好方法(可能是符号设置中最好的)。让我们把它做成一般,避免代码重复。
我们需要的是编写一个更高阶的函数。我不会做任何全新的事情,但只会打包你的解决方案,使其更普遍适用:
Clear[tableGen];
tableGen[f_, iter : {i_Symbol, __}, addif : Except[_List] : (True &)] :=
Module[{sowTag},
If[# === {}, #, First@#] &@
Last@Reap[Do[If[addif[#], Sow[#,sowTag]] &[f[i]], iter],sowTag]];
使用Do
优于For
的优点是循环变量是动态本地化的(因此,在Do
范围之外没有对它进行全局修改),以及迭代器语法Do
的{{1}}更接近Table
(Do
也略快)。
现在,这是用法
In[56]:= tableGen[Prime, {i, 10}, PrimeQ[# + 2] &]
Out[56]= {3, 5, 11, 17, 29}
In[57]:= tableGen[Prime, {i, 3, 10}, PrimeQ[# + 1] &]
Out[57]= {}
In[58]:= tableGen[Prime, {i, 10}]
Out[58]= {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
修改强>
这个版本更接近你提到的语法(它需要一个表达式而不是一个函数):
ClearAll[tableGenAlt];
SetAttributes[tableGenAlt, HoldAll];
tableGenAlt[expr_, iter_List, addif : Except[_List] : (True &)] :=
Module[{sowTag},
If[# === {}, #, First@#] &@
Last@Reap[Do[If[addif[#], Sow[#,sowTag]] &[expr], iter],sowTag]];
它还有一个额外的好处,你甚至可以在全局定义迭代器符号,因为它们是未经评估和动态本地化的。使用示例:
In[65]:= tableGenAlt[Prime[i], {i, 10}, PrimeQ[# + 2] &]
Out[65]= {3, 5, 11, 17, 29}
In[68]:= tableGenAlt[Prime[i], {i, 10}]
Out[68]= {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
请注意,由于语法现在不同,我们必须使用Hold
- 属性来防止传递的表达式expr
过早评估。
编辑2
Per @ Simon的请求,这里是许多维度的概括:
ClearAll[tableGenAltMD];
SetAttributes[tableGenAltMD, HoldAll];
tableGenAltMD[expr_, iter__List, addif : Except[_List] : (True &)] :=
Module[{indices, indexedRes, sowTag},
SetDelayed @@ Prepend[Thread[Map[Take[#, 1] &, List @@ Hold @@@ Hold[iter]],
Hold], indices];
indexedRes =
If[# === {}, #, First@#] &@
Last@Reap[Do[If[addif[#], Sow[{#, indices},sowTag]] &[expr], iter],sowTag];
Map[
First,
SplitBy[indexedRes ,
Table[With[{i = i}, Function[Slot[1][[2, i]]]], {i,Length[Hold[iter]] - 1}]],
{-3}]];
由于我必须将Sow
索引与添加的值一起使用,然后根据索引拆分生成的平面列表,因此它相当简单。以下是使用示例:
{i, j, k} = {1, 2, 3};
tableGenAltMD[i + j + k, {i, 1, 5}, {j, 1, 3}, {k, 1, 2}, # < 7 &]
{{{3, 4}, {4, 5}, {5, 6}}, {{4, 5}, {5, 6}, {6}}, {{5, 6}, {6}}, {{6}}}
我将值赋给i,j,k
迭代器变量,以说明此函数本地化迭代器变量,并且对它们的可能全局值不敏感。要检查结果,我们可以使用Table
,然后删除不满足条件的元素:
In[126]:=
DeleteCases[Table[i + j + k, {i, 1, 5}, {j, 1, 3}, {k, 1, 2}],
x_Integer /; x >= 7, Infinity] //. {} :> Sequence[]
Out[126]= {{{3, 4}, {4, 5}, {5, 6}}, {{4, 5}, {5, 6}, {6}}, {{5, 6}, {6}}, {{6}}}
请注意,我没有进行大量检查,因此当前版本可能包含错误,需要进行更多测试。
编辑3 - BUG FIX
请注意重要的错误修复:在所有功能中,我现在使用带有自定义唯一标记的Sow
和Reap
。如果没有此更改,当他们评估的表达式也使用Sow
时,函数将无法正常工作。这是Reap
- Sow
的一般情况,类似于例外情况(Throw
- Catch
)。
编辑4 - SyntaxInformation
由于这是一个非常有用的功能,因此很好地使它更像内置函数。首先,我们通过
添加语法突出显示和基本参数检查SyntaxInformation[tableGenAltMD] = {"ArgumentsPattern" -> {_, {_, _, _., _.}.., _.},
"LocalVariables" -> {"Table", {2, -2}}};
然后,添加用法消息允许菜单项“Make Template”(Shift+Ctrl+k
)起作用:
tableGenAltMD::usage = "tableGenAltMD[expr,{i,imax},addif] will generate \
a list of values expr when i runs from 1 to imax, \
only including elements if addif[expr] returns true.
The default of addiff is True&."
可以在this gist中找到更完整且格式化的使用情况消息。
答案 1 :(得分:2)
我认为Reap / Sow方法在内存使用方面可能是最有效的。一些替代方案可能是:
DeleteCases[(With[{p=Prime[#]},If[PrimeQ[p+2],p,{}] ] ) & /@ Range[K]),_List]
或者(这个可能需要某种DeleteCases来消除Null结果):
FoldList[[(With[{p=Prime[#2]},If[PrimeQ[p+2],p] ] )& ,1.,Range[2,K] ]
两者都在内存中保存了一个大整数1到K的列表,但是Primes的范围是在With []结构中。
答案 2 :(得分:2)
是的,这是另一个答案。另一种包括Reap / Sow方法和FoldList方法的替代方案是使用Scan。
result = {1};
Scan[With[{p=Prime[#]},If[PrimeQ[p+2],result={result,p}]]&,Range[2,K] ];
Flatten[result]
同样,这涉及一长串整数,但中间的Prime结果不会被存储,因为它们位于With的本地范围内。因为p是With函数范围内的常量,所以可以使用With而不是Module,并获得一点速度。
答案 3 :(得分:2)
您可以尝试这样的事情:
Clear[f, primesList]
f = With[{p = Prime[#]},Piecewise[{{p, PrimeQ[p + 2]}}, {}] ] &;
primesList[k_] := Union@Flatten@(f /@ Range[k]);
如果你想要素数p
和素数p+2
,那么解决方案就是
Clear[f, primesList]
f = With[{p = Prime[#]},Piecewise[{{p, PrimeQ[p + 2]}}, {}] ] &;
primesList[k_] :=
Module[{primes = f /@ Range[k]},
Union@Flatten@{primes, primes + 2}];
答案 4 :(得分:1)
好吧,有人必须在某个地方为完整的表格大小分配内存,因为事先不知道最终的大小。
在函数式编程之前的好时光:),通过分配最大数组大小,然后使用单独的索引插入它来解决这类问题,因此不会产生任何漏洞。喜欢这个
x=Table[0,{100}]; (*allocate maximum possible*)
j=0;
Table[ If[PrimeQ[k+2], x[[++j]]=k],{k,100}];
x[[1;;j]] (*the result is here *)
{1,3,5,9,11,15,17,21,27,29,35,39,41,45,51,57,59,65,69,71,77,81,87,95,99}
答案 5 :(得分:0)
以下是使用NextPrime
的另外两种选择:
pairs1[pmax_] := Select[Range[pmax], PrimeQ[#] && NextPrime[#] == 2 + # &]
pairs2[pnum_] := Module[{p}, NestList[(p = NextPrime[#];
While[p + 2 != (p = NextPrime[p])];
p - 2) &, 3, pnum]]
和Reap / Sow解决方案的修改,允许您指定最大素数:
pairs3[pmax_] := Module[{k,p},
Reap[For[k = 1, (p = Prime[k]) <= pmax, k++,
If[PrimeQ[p + 2], Sow[p]]]][[-1, 1]]]
以上是按照提高速度的顺序。
In[4]:= pairs2[10000]//Last//Timing
Out[4]= {3.48,1261079}
In[5]:= pairs1[1261079]//Last//Timing
Out[5]= {6.84,1261079}
In[6]:= pairs3[1261079]//Last//Timing
Out[7]= {0.58,1261079}