Хотелось бы примерно такого:
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.
Дополнение: Для правильной работы для каждого он должен поддерживать Break[] и Continue[]. Я не знаю, как это реализовать. Возможно, это нужно будет как-то реализовать в терминах For, While или Do, поскольку это единственные конструкции цикла, поддерживающие Break[] и Continue[].
И еще одна проблема с ответами: они кушают Return[]. То есть, если вы используете цикл ForEach в функции и хотите вернуться из функции из цикла, вы не можете. Выдача Return внутри цикла ForEach, похоже, работает как Continue[]. Это просто (подождите) бросило меня в замешательство.





Встроенный 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. *)
@Leonid: Звучит правильно, хотя я чувствую, что была какая-то причина - возможно, небольшая ошибка - что я в конечном итоге добавил дополнительные Evaluate и Hold и еще много чего. Хотите добавить свою упрощенную версию в качестве ответа, и я протестирую ее и помечу как принятую, если смогу убедить себя, что это лучше? Еще раз спасибо за помощь!
Конечно, почему бы и нет. Я гораздо меньше озабочен тем, чтобы получить за это признание, чем обеспечение того, чтобы заблуждения не распространялись, поэтому я был бы просто счастлив, если (как только вы убедитесь, что это правильно), вы могли бы сделать это более заметным для других, посещающих эту страницу любыми способами, не обязательно отмечая его галочкой. Позвольте мне просто признать, что я использовал ReleaseHold [Hold []] и Evaluate аналогичным образом в течение некоторого времени, пока я не понял оценку Mathematica намного лучше и не понял, что они не нужны (по крайней мере, для такого рода случаев использования).
Встроенная функция карты делает именно то, что вы хотите. Его можно использовать в развернутой форме:
Карта [Печать, {1,2,3}]
или короткая рука
Печать / @ {1,2,3}
Во втором случае вы должны использовать «Print [Times @@ #] & / @ {{1,10}, {2,20}, {3,30}}»
Я бы рекомендовал прочитать справку Mathematica по Map, MapThread, Apply и Function. К ним можно немного привыкнуть, но однажды вы никогда не захотите возвращаться!
Спасибо! Карта действительно почти всегда то, что вам нужно для таких вещей. Scan фактически идентичен Map, за исключением того, что он используется строго для побочных эффектов - он не возвращает список.
В новых версиях 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 явным, когда я подавляю возвращаемое значение функции. РЕДАКТИРОВАТЬ: Я исправил ошибку, указанную ниже; Мне всегда нравится использовать With для интерполяции вычисленных выражений в формы Hold*.
Я считаю, что это не деструктурирует. То есть, вы не можете перебирать структуры данных, в которых вы даете частям структур данных их собственные имена, как в моем примере перебора пар {i, j}. Я считаю это очень полезным.
Есть ли какие-либо преимущества у вашего метода Hold / ReleaseHold по сравнению с моей версией, в которой используется Evaluate? Думаю, в любом случае вам нужно установить атрибут HoldAll? По сути, это версия макросов Lisp для Mathematica. Было бы неплохо иметь более общий / канонический способ сделать это. Может это то, что ты дал?
Второе правило выполняет деструктуризацию, потому что Set [] деструктурирует автоматически: {i, j} = {3, 4} установит i в 3 и j в 4. Я обычно предпочитаю подход Hold / ReleaseHold, когда это возможно, особенно если я могу напрямую подставлять вещи в качестве параметров функций, потому что это избавляет меня от необходимости думать о том, что удерживается, а что нет. И да, это в основном несколько неуклюжий способ создания макросов Lisp.
Спасибо, это поучительное обсуждение! Я считаю, что моя версия является еще более общей, поскольку она может разрушать произвольные вложенные структуры. Можно ли для этого пропатчить вашу версию?
Да, я отредактировал свой ответ, чтобы разрешить произвольную деструктуризацию на основе шаблонов. Однако чем больше я думаю об этом, тем больше думаю, что кейсы - это именно то, что вам нужно.
Вау, я только что провел тест скорости, и ваша версия с Hold и Cases кажется более чем в 4 раза быстрее, чем моя версия с Scan / Replace / Evaluate. Спасибо большое! Я делаю ваш принятый ответ сейчас.
К сожалению, я обнаружил ошибку в вашей версии Hold / Cases. Если у меня есть заранее определенный список, который я хочу перебрать, то из-за удержаний он не работает. Например, nums = {1,23}; ForEach[i_, nums, Print[i]];, я думаю, вам просто нужно сделать Evaluate[list], как я сделал в своей версии. Хотите обновить свой ответ?
@Pillsy: Возможно, я пришел к неверному выводу. У меня проблемы с воспроизведением предполагаемой ошибки. Знаете ли вы о случае, когда произойдет сбой без только что добавленного блока With?
@Pillsy: Теперь я считаю, что предполагаемая ошибка была ложной тревогой. Я изменил LHS определения ForEach на ForEach[pat_, lst_List, bod_], чего нельзя сделать для функции HoldAll. Например, SetAttributes[f, HoldAll]; f[x_List]:=0; abc = {1,2,3}; f[abc] не оценивает.
В Mathematica есть функции карты, поэтому допустим, у вас есть функция Func, принимающая один аргумент. Тогда просто напишите
Func /@ list
Print /@ {1, 2, 3, 4, 5}
Возвращаемое значение - это список функций, примененных к каждому элементу в списке.
PrimeQ /@ {10, 2, 123, 555}
вернет {False,True,False,False}
Спасибо, да, Map (/@) обычно то, что вам нужно. См. Мой комментарий к ответу, опубликованному 23 ноября.
Я на много лет опоздал на вечеринку, и это, возможно, скорее ответ на «мета-вопрос», но то, с чем многим людям изначально трудно, когда программирование на 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) без надобности.
Учитывая большой массив из 100000 пар, 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, а в том, что вы часто можете получить те же результаты более эффективно и элегантно, работая с функциональным мышлением, а не со структурным. .
Красиво сказано. Спасибо за этот вклад. Пример того, где я считаю ForEach полезным, - это создание хеш-таблицы. Скажем, у меня есть список структур данных; тогда я мог бы пройти через них, скажем, с ForEach[{i_, {j_, k_}}, listofstuff, hash[f[i,k] = g[j,k]].
Благодаря Pillsy и Леонид Шифрин вот что я сейчас использую:
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) *)
Дэниел, так как ваш each - HoldAll, использование таких шаблонов, как lst_List, подведет вас, если список хранится в переменной или является результатом оценки какой-либо другой функции, например: a = {b, c, d}; {b, c, d} = {1,2,3}; каждый [x_, a, x = 1]. Фактически, в этом случае ваше первое def не будет совпадать, а второе def вас подведет. Я обсуждал здесь причину, по которой сопоставление с образцом мешает удерживаемым атрибутам: mathprogramming-intro.org/book/node408.html. Я бы просто придерживался SetAttributes [each, HoldAll]; каждый [p_, l_, b_]: = (Cases [Unevaluated [l], p:> b];); , и нет Null в конце
@Leonid: Я думал, что я просто не ожидал, что элементы в списке останутся неоцененными, если я не перейду в явный список. В вашем примере меня не подводят, как вы говорите, потому что, передавая "a", я действительно передаю {1,2,3}, поэтому имеет смысл, что он не может установить эти элементы.
Ты прав. На этот раз я поспешил с комментариями и не совсем понял семантику вашей функции. Также в моей версии это не будет работать должным образом, так как очень сложно «частично оценить», так что a оценивается в {b, c, d}, но они не оцениваются дальше, и даже это было бы неоднозначно. Я могу удалить свой комментарий. Что лучше всего делать с такими неправильными комментариями в SO - оставить их или удалить?
@ Леонид: Уф! Спасибо! Что касается комментариев, если бы он был в вопросе или ответе, я бы определенно сказал удалить. Но в комментариях я не уверен. Я должен добавить к ответу еще несколько пояснений, чтобы в первую очередь предотвратить такую путаницу. (Или не стесняйтесь делать это, если у вас достаточно магических очков, чтобы редактировать ответы других людей!)
Вот небольшое улучшение, основанное на последнем ответе 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]
Дэниел, вам здесь не нужна Evaluate, а также пара ReleaseHold / Hold. Извините за повторение, но похоже, что вы не большой поклонник лишнего набора текста. Вычисление не требуется, поскольку Cases не содержит аргументов, а HoldAll для ForEach гарантирует только сохранение аргументов внутри ForEach. Дела инициируют новый цикл оценки, на который этот HoldAll не влияет. ReleaseHold @ Hold [] в целом бессмысленен (как я уже отмечал в другом месте), и в частности здесь, поскольку SetDelayed не оценивает свою относительную влажность. На самом деле, вы можете захотеть обратного: оберните свой lst в Unevaluated - для меня это имеет больше смысла