22

我想要这样的东西:

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]]

那应该打印 和 的bc

附录:为了正确地做for-each,它应该支持Break[]Continue[]。我不确定如何实现。也许它需要以某种方式用 For、While 或 Do 来实现,因为这些是唯一支持Break[]and的循环结构Continue[]

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

4

7 回答 7

11

我在这里聚会迟到了几年,这可能更像是对“元问题”的回答,但是当使用 Mathematica(或其他函数式语言)编程时,许多人最初遇到的困难来自功能而非结构的观点。Mathematica 语言具有结构构造,但其核心是功能性的。

考虑你的第一个例子:

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

正如一些人指出的那样,这可以在功能上表示为Scan[Print, {1,2,3}]or 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} 的实例并将它们分配给iand j)。

给定一个包含 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不仅仅是规则替换,还需要一个接一个地构建匹配元素的输出。事实证明,我们可以通过不同的方式分解问题,并利用, 并支持向量化操作这一Times事实,做得更好。Listable

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]}

所以,因为Timesis Listable,如果我们将数字对作为两个单独的数组:

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 中也是如此,而是当您以功能性思维方式而不是结构性思维方式工作时,您通常可以更有效、更优雅地获得相同的结果。

于 2010-03-05T23:40:30.633 回答
9

较新版本的 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 显式。编辑:我修复了下面指出的错误;我总是喜欢使用With将求值表达式插入到Hold*表单中。

于 2009-08-11T14:39:20.677 回答
8

内置的Scan基本上是这样做的,虽然它更难看:

    扫描[打印[#]&, {1,2,3}]

当您想要解构元素时,它尤其难看:

    扫描[打印[#[[1]] * #[[2]]]&, {{1,10}, {2,20}, {3,30}}]

以下函数通过转换patternbody的每个元素来避免丑陋list

SetAttributes[ForEach, HoldAll];
ForEach[pat_, lst_, bod_] := 扫描[替换[#, 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.        *)
于 2008-10-01T23:28:17.603 回答
3

内置的地图功能正是您想要的。它可以以长形式使用:

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

或速记

打印 /@ {1,2,3}

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

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

于 2008-11-23T00:00:46.497 回答
2

这是基于 dreeves 的最后一个答案的轻微改进,它允许指定不带空白的模式(使语法类似于 Table 或 Do 等其他函数)并使用 Cases 的 level 参数

SetAttributes[ForEach,HoldAll];
ForEach[patt_/; FreeQ[patt, Pattern],list_,expr_,level_:1] :=
   Module[{pattWithBlanks,pattern},
      pattWithBlanks = patt/.(x_Symbol/;!MemberQ[{"System`"},Context[x]] :> pattern[x,Blank[]]);
      pattWithBlanks = pattWithBlanks/.pattern->Pattern;

      Cases[Unevaluated@list, pattWithBlanks :> expr, {level}];
      Null
   ];

测试:

ForEach[{i, j}, {{1, 10}, {2, 20}, {3, 30}}, Print[i*j]]
ForEach[i, {{1, 10}, {2, 20}, {3, 30}}, Print[i], 2]
于 2011-08-31T15:57:11.213 回答
1

Mathematica 有 map 函数,所以假设你有一个函数Func接受一个参数。然后就写

Func /@ list

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

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

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

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

于 2009-08-24T16:48:44.363 回答
1

感谢PillsyLeonid Shifrin,这就是我现在使用的:

SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_List, bod_] :=               (*  converts pattern to body for  *)
  (Cases[Unevaluated@lst, pat:>bod]; Null); (*   each element of list.        *)
each[p_, l_, b_] := (Cases[l, p:>b]; Null); (* (Break/Continue not supported) *)
于 2011-01-15T16:30:38.153 回答