Цикл 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 должен оставаться неоцененным, если это список.

Если неоцененная форма не является списком, она должна оценить второй аргумент. Например:

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

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

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

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

Ответы

Ответ 1

Благодаря Pillsy и Леониду Шифрину, вот что я сейчас использую:

SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_List, bod_] :=               (*  converts pattern to body for  *)
  (Cases[[email protected], pat:>bod]; Null); (*   each element of list.        *)
each[p_, l_, b_] := (Cases[l, p:>b]; Null); (* (Break/Continue not supported) *)

Ответ 2

Встроенный 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]&, [email protected]]

который можно использовать как в примере в вопросе.

PS: принятый ответ заставил меня переключиться на это, и это то, что я использовал с тех пор, и, похоже, он отлично работает (за исключением оговорки, которую я добавил к вопросу):

SetAttributes[ForEach, HoldAll];             (* ForEach[pattern, list, body]   *)
ForEach[pat_, lst_, bod_] := ReleaseHold[    (*  converts pattern to body for  *)
  Hold[Cases[[email protected], pat:>bod];]];     (*   each element of list.        *)

Ответ 3

Более новые версии 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 = #},...] & позволяет переменным в вашем выражении быть привязанным к любым значениям, которые вы хотите.

В ответ на вопрос ниже, вы можете изменить этот подход, чтобы обеспечить более произвольное деструктурирование с использованием DownValues ​​уникального символа.

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

Ответ 4

Я опаздываю на вечеринку здесь, и это, возможно, больше ответ на "мета-вопрос", но с некоторым количеством людей изначально трудно сработать, когда приближается программирование в 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]:= (* [email protected]@@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[[email protected]@Transpose[array];]]
Out[8]= 0.020391

Мораль этого эпоса заключается не в том, что ForEach не является ценной конструкцией вообще или даже в Mathematica, но что вы можете часто получать те же результаты более эффективно и элегантнее, когда работаете в функциональном мышлении, а не структурной.

Ответ 5

Встроенная функция Map делает именно то, что вы хотите. Его можно использовать в длинной форме:

Карта [Печать, {1,2,3}]

или короткий

Печать/@{1,2,3}

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

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

Ответ 6

Ниже приведено небольшое улучшение, основанное на последнем ответе трехэтажек, который позволяет указать шаблон без Blank (делая синтаксис похожим на другие функции, такие как 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[[email protected], 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]

Ответ 7

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

Func /@ list

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

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

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

вернет {False,True,False,False}