Цикл ForEach в Mathematica


Я бы хотел что-то вроде этого:

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

Или, в более общем смысле, для деструкции произвольного материала в списке, который вы просматриваете, например:

each[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]
Обычно вы хотите использовать Map или другие чисто функциональные конструкции и избегать нефункционального стиля программирования, где вы используете побочные эффекты. Но вот пример, где я думаю, что конструкция для каждого в высшей степени полезна:

Допустим, у меня есть список опций (правил), которые связывают символы с выражениями, например

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[i_, Rest[{a,b,c}], Print[i]]

, которые должны вывести значения b и c.

Добавление : чтобы сделать для-каждого должным образом, он должен поддерживать Break[] и Continue[]. Я не знаю, как это осуществить. Возможно, его нужно будет каким-то образом реализовать в терминах For, While или Do, поскольку это единственные конструкции цикла, которые поддерживают Break[] и Continue[].

И еще одна проблема с ответами до сих пор: они едят Return [] s. то есть, если вы используете ForEach цикл в функции и хотите вернуться из функции из цикла, вы не можете. выдача возврата внутри цикла ForEach, кажется, работает как Continue[]. Это просто (подождите его) бросило меня за петлю.

7 17

7 ответов:

Более новые версии 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]]];

Это использует символы в качестве имен переменных, а не паттернов, но именно так различные встроенные элементы управления структуры, как сделать[] и[] работы.

Функции HoldAll [] позволяют создавать довольно широкий набор пользовательских структур управления. ReleaseHold [Держать[...]] обычно самый простой способ собрать кучу кода Mathematica, который будет оценен позже, и заблокировать[{x = #}, ...] & позволяет привязывать переменные в теле выражения к любым значениям, которые вы хотите.

В ответ на вопрос дривза ниже, вы можете изменить этот подход, чтобы разрешить более произвольное разрушение с помощью Понижающие значения уникального символа.

ForEach[patt_, list_, expr_] := 
  ReleaseHold[Hold[
     Module[{f}, 
       f[patt] := expr; 
       Scan[f, list]]]]
Однако на данном этапе, я думаю, вам лучше построить что-то поверх дел.
ForEach[patt_, list_, expr_] :=
  With[{bound = list},
    ReleaseHold[Hold[
       Cases[bound,
         patt :> expr]; 
       Null]]]

Мне нравится делать Null явным, когда я подавляю возвращаемое значение функции. EDIT : я исправил ошибку, на которую указал ниже дривз; мне всегда нравится использовать 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
Мораль этой эпопеи не в том, что она не является ценной конструкцией вообще или даже в математике, а в том, что вы часто можете получить те же самые результаты более эффективно и более элегантно, когда вы работаете в функциональном мышлении, а не в структурном.

Встроенный 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: принятый ответ побудил меня переключиться на это, что я использую с тех пор, и это, кажется, работает отлично (за исключением оговорки I приложено к вопросу):

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

Или короткая рука

Print /@ {1,2,3}

Во втором случае вы бы использовали " Print[Times@@#]&/@{{1,10}, {2,20}, {3,30}}"

Я бы рекомендовал прочитать справку Mathematica по Map, MapThread, Apply и Function. Они могут немного привыкнуть, но как только вы это сделаете, вы никогда не захотите возвращаться!

Здесь есть небольшое улучшение, основанное на последнем ответе dreeves, которое позволяет указать шаблон без пробелов (что делает синтаксис похожим на другие функции, такие как Table или Do) и использует аргумент уровня 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
   ];

Тесты:

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

У Mathematica есть функции отображения, поэтому предположим, что у вас есть функция Func, принимающая один аргумент. Тогда просто напишите

Func /@ list

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

Возвращаемое значение - это список функций, применяемых к каждому элементу в списке in.

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

Вернется {False,True,False,False}

Спасибо Пилси и Леониду Шифрину, Вот что я сейчас использую:

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