Цикл ForEach в Mathematica - PullRequest
       15

Цикл ForEach в Mathematica

17 голосов
/ 02 октября 2008

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

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[] с. То есть, если вы используете цикл ForEach в функции и хотите вернуться из функции внутри цикла, вы не сможете. Возврат Return внутри цикла ForEach, похоже, работает как Continue[]. Это просто (подожди) заставило меня замкнуться.

Ответы [ 7 ]

7 голосов
/ 06 марта 2010

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

7 голосов
/ 11 августа 2009

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

7 голосов
/ 02 октября 2008

Встроенный 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.        *)
3 голосов
/ 23 ноября 2008

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

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

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

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

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

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

2 голосов
/ 31 августа 2011

Вот небольшое улучшение, основанное на последнем ответе 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]
1 голос
/ 15 января 2011

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

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) *)
1 голос
/ 24 августа 2009

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

Func /@ list

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

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

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

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

...