Цикл ForEach в Mathematica
-
03-07-2019 - |
Вопрос
Мне бы хотелось что-то вроде этого:
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]]
Это должно напечатать значения b
и c
.
Приложение:Чтобы правильно выполнить for-each, он должен поддерживать Break[]
и Continue[]
.Я не уверен, как это реализовать.Возможно, это нужно будет каким-то образом реализовать с помощью For, While или Do, поскольку это единственные конструкции цикла, которые поддерживают Break[]
и Continue[]
.
И еще одна проблема с ответами на данный момент:они едят Return[]
с.То есть, если вы используете цикл ForEach в функции и хотите вернуться из функции из цикла, вы не можете этого сделать.Выдача Return внутри цикла ForEach, похоже, работает как Continue[]
.Это просто (подождите) поставило меня в тупик.
Решение 7
Благодаря Пиллси и Леонид Шифрин, вот что я сейчас использую:
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) *)
Другие советы
В более новых версиях 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]]]]
Однако на данный момент я думаю, что вам, возможно, лучше построить что-нибудь на основе Cases.
ForEach[patt_, list_, expr_] :=
With[{bound = list},
ReleaseHold[Hold[
Cases[bound,
patt :> expr];
Null]]]
Мне нравится явно указывать Null, когда я подавляю возвращаемое значение функции. РЕДАКТИРОВАТЬ:Я исправил ошибку, указанную ниже;мне всегда нравится использовать 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
Мораль этой эпопеи не в этом. ForEach
не является ценной конструкцией в целом или даже в системе Mathematica, но зачастую вы можете получить те же результаты более эффективно и элегантно, если работаете с функциональным, а не структурным мышлением.
Встроенный 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]
который можно использовать, как в примере в вопросе.
ПС:Принятый ответ побудил меня переключиться на этот вариант, который я использую с тех пор, и, похоже, он отлично работает (за исключением оговорки, которую я добавил к вопросу):
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}]
или сокращенно
Распечатать /@ {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}
Возвращаемое значение представляет собой список функций, примененных к каждому элементу в списке.
PrimeQ /@ {10, 2, 123, 555}
вернется {False,True,False,False}