在Mathematica中生成一个列表,并对每个元素进行条件测试

时间:2011-06-16 06:28:15

标签: wolfram-mathematica

假设我们要生成一个素数列表 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] &]

6 个答案:

答案 0 :(得分:18)

我将更多地解释为关于自动化和软件工程的问题,而不是关于手头的具体问题,并且已经发布了大量解决方案。 ReapSow是收集中间结果的好方法(可能是符号设置中最好的)。让我们把它做成一般,避免代码重复。

我们需要的是编写一个更高阶的函数。我不会做任何全新的事情,但只会打包你的解决方案,使其更普遍适用:

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}}更接近TableDo也略快)。

现在,这是用法

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

请注意重要的错误修复:在所有功能中,我现在使用带有自定义唯一标记的SowReap。如果没有此更改,当他们评估的表达式也使用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}