Цикл 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}