【问题标题】:ForEach loop in MathematicaMathematica 中的 ForEach 循环
【发布时间】:2008-10-01 23:25:43
【问题描述】:

我想要这样的东西:

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[]Continue[] 的循环结构。

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

【问题讨论】:

    标签: loops language-features wolfram-mathematica


    【解决方案1】:

    我在这里聚会迟到了几年,这可能更像是对“元问题”的回答,但是当使用 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} 的实例并将它们分配给 @ 987654329@ 和 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 所做的工作不仅仅是规则替换,必须逐个构建匹配元素的输出。事实证明,我们可以通过不同的方式分解问题,并利用TimesListable 的事实,并支持向量化操作,从而做得更好。

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

    所以,由于TimesListable,如果我们将数字对作为两个单独的数组:

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

    【讨论】:

    • 说得很漂亮。感谢您的贡献。我发现 ForEach 有用的一个例子是创建一个哈希表。假设我有一个数据结构列表;然后我可以通过它们,比如说,ForEach[{i_, {j_, k_}}, listofstuff, hash[f[i,k] = g[j,k]]
    【解决方案2】:

    较新版本的 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* 表单。

    【讨论】:

    • 我相信这不会破坏结构。即,您不能迭代数据结构,在其中给数据结构的各个部分赋予自己的名称,就像在我的迭代 {i,j} 对的示例中一样。我觉得这非常有用。
    • 您的 Hold/ReleaseHold 方法与我使用 Evaluate 的版本相比有什么优势吗?我想无论哪种方式你都必须设置 HoldAll 属性?这基本上是 Mathematica 的 Lisp 宏版本。有一种更通用/规范的方式来做这件事会很好。也许这就是你给的?
    • 第二条规则进行解构,因为 Set[] 会自动解构: {i, j} = {3, 4} 会将 i 设置为 3 并将 j 设置为 4。我通常更喜欢 Hold/ReleaseHold尽可能接近,特别是如果我可以直接将东西替换为函数参数,因为它让我不必考虑什么是持有的,什么不是。是的,这基本上是一种有点笨拙的 Lisp 宏。
    • 谢谢,这是一个有启发性的讨论!我相信我的版本仍然更通用,因为它可以解构任意嵌套结构。你的版本可以打补丁吗?
    • 是的,我已经编辑了我的答案以允许任意的、基于模式的解构。不过,我想得越多,我就越觉得 Cases 正是你想要的。
    【解决方案3】:

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

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

    当你想解构元素的时候特别难看:

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

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

    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.        *)
    

    【讨论】:

    • Daniel,这里不需要 Evaluate 以及 ReleaseHold/Hold 对。抱歉重复了,但看起来你不是额外打字的忠实粉丝。不需要评估,因为 Cases 不包含参数,并且 ForEach 的 HoldAll 仅保证在 ForEach 中包含 args。 Cases 启动一个新的评估周期,HoldAll 对此无效。 ReleaseHold@Hold[] 通常是无意义的(正如我在其他地方指出的那样),尤其是在这里,因为 SetDelayed 不评估其 r.h.s.事实上,您可能想要相反的结果:将您的 lst 包裹在 Unevaluated - 对我来说更有意义
    • @Leonid:这听起来不错,但我觉得有一些原因——也许是一个微妙的错误——我最终添加了额外的 Evaluate 和 Hold 等等。想要添加您的简化版本作为答案,如果我能说服自己它更好,我将对其进行测试并将其标记为接受?再次感谢您的帮助!
    • 当然,为什么不呢。我不太关心为它赢得荣誉,而不是确保误解不会传播,所以如果(一旦你确信这是正确的)你可以让访问此页面的其他人更容易看到这一点,我会很高兴,通过任何方式,不一定通过复选标记它。让我承认我以类似的方式使用 ReleaseHold[Hold[]] 和 Evaluate 有一段时间了,直到我更好地理解 Mathematica 评估并意识到它们是不必要的(至少对于这种用例而言)。
    【解决方案4】:

    内置的地图功能完全符合您的要求。可以长形式使用:

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

    或速记

    打印 /@ {1,2,3}

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

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

    【讨论】:

    • 谢谢!地图确实几乎总是你想要的东西。 Scan 实际上与 Map 相同,只是它严格用于副作用——它不返回列表。
    【解决方案5】:

    这是基于 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]
    

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

      • 谢谢,是的,Map (/@) 通常是您想要的。不过,请参阅我对 11 月 23 日发布的答案的评论。
      【解决方案7】:

      感谢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) *)
      

      【讨论】:

      • Daniel,因为你的 each 是 HoldAll,如果列表存储在变量中或者是其他函数的评估结果,使用 lst_List 之类的模式会让你失望,例如:a = {b ,光盘}; {b,c,d} = {1,2,3};每个[x_,a,x=1]。事实上,在这种情况下,您的第一个 def 将不匹配,然后第二个 def 会让您失望。模式匹配干扰 Hold-attributes 的原因,我在这里讨论过:mathprogramming-intro.org/book/node408.html。我会坚持使用 SetAttributes[each, HoldAll];每个[p_,l_,b_]:=(案例[未评估[l],p:> b];); , 最后没有 Null
      • @Leonid:我的想法是,除非我传入一个明确的列表,否则我不会期望列表中的项目保持未评估状态。在您的示例中,正如您所说,我并没有失望,因为通过传入“a”,我实际上是在通过 {1,2,3} 所以它无法设置这些元素是有道理的。
      • 你是对的。这次我太快发表评论了,并没有完全理解你的函数的语义。同样在我的版本中,它不会按预期工作,因为“部分评估”非常困难,因此 a 评估为 {b,c,d} 但它们不会进一步评估,即使这样也是模棱两可的。我可以删除我的评论。在 SO 上处理这些错误的 cmets 最好的办法是什么 - 保留它们还是删除它们?
      • @Leonid:呸!谢谢!至于 cmets,如果它在问题或答案中,我肯定会说删除。但是我不确定。我应该在答案中添加更多说明,以首先避免这种混乱。 (或者,如果您有足够的魔法点来编辑其他人的答案,也可以随意这样做!)
      猜你喜欢
      • 2021-05-28
      • 1970-01-01
      • 2016-09-26
      • 2012-07-19
      • 2021-11-16
      • 2013-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多