Mathematica中的ForEach循环

时间:2020-03-06 15:00:11  来源:igfitidea点击:

我想要这样的东西:

each[i_, {1,2,3},
  Print[i]
]

或者,更一般而言,要破坏我们要遍历的列表中的任意内容,例如:

each[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]

通常,我们想使用Map或者其他纯函数式构造,并避免使用副作用的非函数式编程风格。但是,在以下示例中,我认为for-each构造极其有用:

假设我有一个将符号与表达式配对的选项(规则)列表,例如

attrVals = {a -> 7, b -> 8, c -> 9}

现在,我想创建一个哈希表,在其中将这些符号明显映射到这些数字。我认为没有比这更干净的方法了

each[a_ -> v_, attrVals, h[a] = v]

其他测试用例

在此示例中,我们转换变量列表:

a = 1;
b = 2;
c = 3;
each[i_, {a,b,c}, i = f[i]]

在上述之后,{a,b,c}应评估为{f [1],f [2],f [3]}。请注意,这意味着如果它是一个列表,那么" each"的第二个参数应保持不变。

如果未评估的形式不是列表,则应评估第二个参数。例如:

each[i_, Rest[{a,b,c}], Print[i]]

那应该打印b和c的值。

附录:为正确进行每一个操作,它应支持Break []和Continue []。我不确定如何实现。也许需要以For,While或者Do的方式来实现它,因为这些是唯一支持Break []和Continue []的循环构造。

到目前为止,答案的另一个问题是:他们吃了Return [] s。也就是说,如果我们在函数中使用ForEach循环,并希望从循环内从函数返回,则不能这样做。在ForEach循环内发出Return似乎像Continue []一样工作。只是(等待它)让我循环了一下。

解决方案

内置的Scan基本上可以做到这一点,尽管比较难看:

Scan[Print[#]&, {1,2,3}]

当我们要分解元素时,这尤其难看:

Scan[Print[#[[1]] * #[[2]]]&, {{1,10}, {2,20}, {3,30}}]

下面的函数通过将list的每个元素的pattern转换为body来避免丑陋。

SetAttributes[ForEach, HoldAll];
ForEach[pat_, lst_, bod_] :=  Scan[Replace[#, pat:>bod]&, Evaluate@lst]

可以在问题的示例中使用。

PS:可接受的答案促使我转向这一点,这是我从那时以来一直在使用的方法,并且似乎效果很好(除了我在问题后面添加的警告):

SetAttributes[ForEach, HoldAll];             (* ForEach[pattern, list, body]   *)
ForEach[pat_, lst_, bod_] := ReleaseHold[    (*  converts pattern to body for  *)
  Hold[Cases[Evaluate@lst, pat:>bod];]];     (*   each element of list.        *)

内置的Map函数完全可以满足需求。它可以长格式使用:

地图[打印,{1,2,3}]

或者简写

打印/ @ {1,2,3}

在第二种情况下,我们将使用"打印[Times @@#]&/ @ {{1,10},{2,20},{3,30}}"

我建议阅读有关Map,MapThread,Apply和Function的Mathematica帮助。他们可能需要一点时间来适应,但是一旦我们适应了,就再也不想回头了!

Mathematica(6.0+)的较新版本具有Do []和Table []的通用版本,它们通过采用替代形​​式的迭代器参数,几乎可以精确地实现我们想要的功能。例如,

Do[
  Print[i],
  {i, {1, 2, 3}}]

就像你一样

ForEach[i_, {1, 2, 3,},
  Print[i]]

或者,如果我们确实喜欢特定的ForEach语法,则可以制作一个HoldAll函数来实现它,如下所示:

Attributes[ForEach] = {HoldAll};

ForEach[var_Symbol, list_, expr_] :=
  ReleaseHold[
    Hold[
      Scan[
        Block[{var = #},
         expr] &,
      list]]];

ForEach[vars : {__Symbol}, list_, expr_] :=
  ReleaseHold[
    Hold[
      Scan[
        Block[vars,
          vars = #;
          expr] &,
      list]]];

它使用符号作为变量名,而不是模式,但这就是Do []和For []等各种内置控件结构的工作方式。

HoldAll []函数使我们可以组合各种自定义控件结构。 ReleaseHold [Hold [...]]通常是组装一堆待稍后评估的Mathematica代码的最简单方法,而Block [{x =#},...]&允许将表达式主体中的变量绑定到我们想要的任何值。

为回答以下有关dreeves的问题,我们可以修改此方法,以使用唯一符号的DownValues进行更多的任意解构。

ForEach[patt_, list_, expr_] := 
  ReleaseHold[Hold[
     Module[{f}, 
       f[patt] := expr; 
       Scan[f, list]]]]

但是,在这一点上,我认为我们最好在Cases之上构建一些东西。

ForEach[patt_, list_, expr_] :=
  With[{bound = list},
    ReleaseHold[Hold[
       Cases[bound,
         patt :> expr]; 
       Null]]]

在抑制函数的返回值时,我喜欢使Null显式。编辑:我修复了下面指出为dreeves的错误;我一直喜欢使用" With"将求值表达式插入" Hold *"形式。

Mathematica具有map函数,因此可以说我们有一个函数" Func",它带有一个参数。然后写

Func /@ list

Print /@ {1, 2, 3, 4, 5}

返回值是应用于列表中每个元素的函数的列表。

PrimeQ /@ {10, 2, 123, 555}

将返回{False,True,False,False}

我在这里参加聚会已经晚了几年,这也许可以更好地回答"元问题",但是许多人最初在使用Mathematica(或者其他功能语言)进行编程时遇到了困难。功能观点而非结构观点。 Mathematica语言具有结构构造,但其功能是核心。

考虑第一个示例:

ForEach[i_, {1,2,3},
  Print[i]
]

正如一些人指出的那样,可以在功能上将其表示为Scan [Print,{1,2,3}]或者Print / @ {1,2,3}(尽管我们应该将Scan优先于Map如前所述,在可能的情况下,但是有时会很烦人,因为没有用于Scan的中缀运算符。

在Mathematica中,通常有十二种方法来完成所有事情,这有时很漂亮,有时很令人沮丧。考虑到这一点,请考虑第二个示例:

ForEach[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]

...从功能的角度来看更有趣。

一种可能的功能解决方案是改为使用列表替换,例如:

In[1]:= {{1,10},{2,20},{3,30}}/.{i_,j_}:>i*j
Out[1]= {10,40,90}

...但是如果列表很大,那么这会不必要地变慢,因为我们正在执行所谓的"模式匹配"(例如,在列表中寻找{a,b}的实例并将其分配给ij)不必要。

给定一个由100,000对组成的大型数组,即array = RandomInteger [{1,100},{10 ^ 6,2}]`,我们可以看一下一些时间:

规则替换非常快:

In[3]:= First[Timing[array /. {i_, j_} :> i*j;]]
Out[3]= 1.13844

...但是如果我们利用每对实际上是List [i,j]的表达式结构,并将Times用作每对的头部,将每个{{i,j }转换成Times [i,j]`:

In[4]:= (* f<list is the infix operator form of Apply[f, list, 1] *)
    First[Timing[Times < array;]]
Out[4]= 0.861267

如上面的ForEach [...]的实现中所使用的,Cases绝对不是最佳选择:

In[5]:= First[Timing[Cases[array, {i_, j_} :> i*j];]]
Out[5]= 2.40212

...因为Cases不仅要执行规则替换,还需要做更多的工作,因此必须逐一构建匹配元素的输出。事实证明,通过对问题进行不同的分解,我们可以做的更好,并利用"时间"是"可列出"的事实,并支持向量化操作。

Listable属性意味着函数f将自动在任何列表参数上进行线程化:

In[16]:= SetAttributes[f,Listable]
In[17]:= f[{1,2,3},{4,5,6}]
Out[17]= {f[1,4],f[2,5],f[3,6]}

因此,由于"时间"是"可列出的",因此如果我们改为将数字对作为两个单独的数组:

In[6]:= a1 = RandomInteger[{1, 100}, 10^6];
        a2 = RandomInteger[{1, 100}, 10^6];

In[7]:= First[Timing[a1*a2;]]
Out[7]= 0.012661

哇,快多了!即使没有将输入作为两个单独的数组提供(或者每对中有两个以上的元素),我们仍然可以做一些优化的事情:

In[8]:= First[Timing[Times@@Transpose[array];]]
Out[8]= 0.020391

这部史诗的寓意不是说" ForEach"总体上甚至不是Mathematica中都不是有价值的结构,而是当我们以实用的思维方式工作时,我们通常可以更有效,更优雅地获得相同的结果,而不是结构一。