我想要这样的事情:
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}}]
以下函数通过转换为每个元素pattern
来避免丑陋.body
list
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. *)
较新版本的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]]]]
不过,在这一点上,我认为你最好在Case之上构建一些东西.
ForEach[patt_, list_, expr_] := With[{bound = list}, ReleaseHold[Hold[ Cases[bound, patt :> expr]; Null]]]
当我压制函数的返回值时,我喜欢使Null显式化.编辑:我修复了下面指出的漏洞; 我总是喜欢使用With
将评估的表达式插入Hold*
表单中.
我在这里参加聚会的时间已经很晚了,这或许更像是对"元问题"的回答,但很多人最初在使用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}的实例并将它们分配给i
和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]}
所以,既然Times
就是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中都不是一个有价值的结构,而是当你在一个功能性思维模式而不是结构性思维模式中工作时,你通常可以更有效,更优雅地获得相同的结果.