ForEach loop in Mathematica

2019-01-18 10:17发布

I'd like something like this:

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

Or, more generally, to destructure arbitrary stuff in the list you're looping over, like:

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

Usually you want to use Map or other purely functional constructs and eschew a non-functional programming style where you use side effects. But here's an example where I think a for-each construct is supremely useful:

Say I have a list of options (rules) that pair symbols with expressions, like

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

Now I want to make a hash table where I do the obvious mapping of those symbols to those numbers. I don't think there's a cleaner way to do that than

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

Additional test cases

In this example, we transform a list of variables:

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

After the above, {a,b,c} should evaluate to {f[1],f[2],f[3]}. Note that that means the second argument to "each" should be held unevaluated if it's a list.

If the unevaluated form is not a list, it should evaluate the second argument. For example:

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

That should print the values of b and c.

Addendum: To do for-each properly, it should support Break[] and Continue[]. I'm not sure how to implement that. Perhaps it will need to somehow be implemented in terms of For, While, or Do since those are the only loop constructs that support Break[] and Continue[].

And another problem with the answers so far: they eat Return[]s. That is, if you are using a ForEach loop in a function and want to return from the function from within the loop, you can't. Issuing Return inside the ForEach loop seems to work like Continue[]. This just (wait for it) threw me for a loop.

7条回答
戒情不戒烟
2楼-- · 2019-01-18 10:40

Mathematica have map functions, so lets say you have a function Functaking one argument. Then just write

Func /@ list

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

The return value is a list of the function applied to each element in the in-list.

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

will return {False,True,False,False}

查看更多
爷、活的狠高调
3楼-- · 2019-01-18 10:44

The built-in Map function does exactly what you want. It can be used in long form:

Map[Print, {1,2,3}]

or short-hand

Print /@ {1,2,3}

In your second case, you'd use "Print[Times@@#]&/@{{1,10}, {2,20}, {3,30}}"

I'd recommend reading the Mathematica help on Map, MapThread, Apply, and Function. They can take bit of getting used to, but once you are, you'll never want to go back!

查看更多
够拽才男人
4楼-- · 2019-01-18 10:45

Thanks to Pillsy and Leonid Shifrin, here's what I'm now using:

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) *)
查看更多
萌系小妹纸
5楼-- · 2019-01-18 10:47

Newer versions of Mathematica (6.0+) have generalized versions of Do[] and Table[] that do almost precisely what you want, by taking an alternate form of iterator argument. For instance,

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

is exactly like your

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

Alterntatively, if you really like the specific ForEach syntax, you can make a HoldAll function that implements it, like so:

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

This uses symbols as variable names, not patterns, but that's how the various built-in control structures like Do[] and For[] work.

HoldAll[] functions allow you to put together a pretty wide variety of custom control structures. ReleaseHold[Hold[...]] is usually the easiest way to assemble a bunch of Mathematica code to be evaluated later, and Block[{x = #}, ...]& allows variables in your expression body to be bound to whatever values you want.

In response to dreeves' question below, you can modify this approach to allow for more arbitrary destructuring using the DownValues of a unique symbol.

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

At this point, though, I think you may be better off building something on top of Cases.

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

I like making Null explicit when I'm suppressing the return value of a function. EDIT: I fixed the bug pointed out be dreeves below; I always like using With to interpolate evaluated expressions into Hold* forms.

查看更多
我欲成王,谁敢阻挡
6楼-- · 2019-01-18 10:50

The built-in Scan basically does this, though it's uglier:

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

It's especially ugly when you want to destructure the elements:

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

The following function avoids the ugliness by converting pattern to body for each element of list.

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

which can be used as in the example in the question.

PS: The accepted answer induced me to switch to this, which is what I've been using ever since and it seems to work great (except for the caveat I appended to the question):

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.        *)
查看更多
戒情不戒烟
7楼-- · 2019-01-18 10:54

Here is a slight improvement based on the last answer of dreeves that allows to specify the pattern without Blank (making the syntax similar to other functions like Table or Do) and that uses the level argument of Cases

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

Tests:

ForEach[{i, j}, {{1, 10}, {2, 20}, {3, 30}}, Print[i*j]]
ForEach[i, {{1, 10}, {2, 20}, {3, 30}}, Print[i], 2]
查看更多
登录 后发表回答