цикл ForEach в Mathematica

17

Мне бы хотелось что-то вроде этого:

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[i_, Rest[{a,b,c}], Print[i]]

Это должно печатать значения b и c.

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

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

    
задан dreeves 21.01.2011 в 21:47
источник

7 ответов

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) *)
    
ответ дан dreeves 23.05.2017 в 14:00
источник
  • Daniel, поскольку каждый из них - HoldAll, использование таких шаблонов, как lst_List, позволит вам сбивать, если список хранится в переменной или является результатом оценки некоторой другой функции, например: a = {b, c, d} ; {b, c, d} = {1,2,3}, каждый [x_, a, x = 1]. На самом деле, в этом случае ваш первый def не будет соответствовать, а затем второй def вас подведет. Причина, по которой совпадение шаблонов вмешивается в Hold-attributes, я обсуждал здесь: mathprogramming-intro.org/book/node408.html. Я бы просто придерживался SetAttributes [каждый, HoldAll]; каждый [p_, l_, b_]: = (случаи [Unevaluated [l], p:> b];); , и нет нулей в конце –  Leonid Shifrin 17.01.2011 в 14:47
  • @Leonid: Я думал, что я просто не ожидаю, что элементы в списке останутся неоценимыми, если я не перейду в явный список. В вашем примере я не поддаюсь, как вы говорите, потому что, передавая «a», я действительно передаю {1,2,3}, поэтому имеет смысл, что он не может установить эти элементы. –  dreeves 17.01.2011 в 16:16
  • Вы правы. Я слишком быстро прокомментировал это время и не получил семантики вашей функции. Также в моей версии это не сработает по желанию, так как очень сложно «частично оценить», так что оценка будет оцениваться в {b, c, d}, но они не будут оцениваться дальше, и даже это будет неоднозначно. Я могу удалить свой комментарий. Что лучше всего делать с такими неправильными комментариями на SO - сохранить их или удалить? –  Leonid Shifrin 17.01.2011 в 16:39
  • @Leonid: Фу! Благодаря! Что касается комментариев, если бы это было в вопросе или ответе, я бы определенно сказал delete. Но комментарии я не уверен. Я должен добавить еще несколько объяснений в ответ, чтобы избежать такой путаницы, в первую очередь. (Или не стесняйтесь делать это, если у вас достаточно магических точек для редактирования ответов других людей!) –  dreeves 17.01.2011 в 17:40
7

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

В ответ на вопрос ниже, вы можете изменить этот подход, чтобы обеспечить более произвольное деструктурирование с использованием 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* .

    
ответ дан Pillsy 05.03.2010 в 20:59
источник
  • Я считаю, что это не делает деструктурирования. То есть вы не можете перебирать структуры данных, где вы даете частям структуры данных свои собственные имена, например, в моем примере итерации по парам {i, j}. Я считаю, что это супер полезно. –  dreeves 11.08.2009 в 22:47
  • Есть ли какое-либо преимущество в методе Hold / ReleaseHold против моей версии, использующей Evaluate? Я предполагаю, что вам нужно установить атрибут HoldAll? Это в основном Mathematica версия макросов Lisp. Было бы неплохо иметь более общий / канонический способ сделать это. Может быть, это то, что вы дали? –  dreeves 11.08.2009 в 22:58
  • Второе правило выполняет деструктурирование, потому что Set [] destructures автоматически: {i, j} = {3, 4} установит i в 3 и j в 4. Обычно я предпочитаю подход Hold / ReleaseHold, особенно если я могу напрямую заменить вещи в качестве параметров функции, потому что это избавляет меня от необходимости думать о том, что удерживается, а что нет. И да, это в основном несколько неуклюжий способ выполнения макросов Lisp. –  Pillsy 12.08.2009 в 02:28
  • Спасибо, это назидательное обсуждение! Я считаю, что моя версия еще более общая, поскольку она может разрушить произвольные вложенные структуры. Может ли ваша версия быть исправлена, чтобы сделать это? –  dreeves 26.08.2009 в 07:41
  • Да, я отредактировал свой ответ, чтобы разрешить произвольное деструктурирование на основе шаблонов. Тем не менее, чем больше я думаю об этом, тем больше я думаю, что «Дела» в значительной степени соответствуют вашим потребностям. –  Pillsy 26.08.2009 в 15:02
Показать остальные комментарии
7

Я опаздываю на вечеринку здесь, и это, возможно, больше ответ на «мета-вопрос», но с некоторым количеством людей изначально трудно сработать, когда приближается программирование в Mathematica (или других функциональных языках) проблема с функциональной, а не структурной точки зрения. Язык Mathematica имеет структурные конструкции, но он функциональный по своей сути.

Рассмотрим ваш первый пример:

ForEach[i_, {1,2,3},
  Print[i]
]

Как отмечалось несколькими людьми, это можно выразить функционально как Scan[Print, {1,2,3}] или Print /@ {1,2,3} (хотя вы, возможно, предпочитаете Scan over 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 выполняет больше работы, чем просто замену правила, необходимо каждый раз создавать вывод соответствующих элементов. Оказывается, мы можем сделать lot лучше, разложив проблему по-разному и воспользуемся тем, что Times % %_о_%% и поддерживает векторную операцию.

Атрибут Listable означает, что функция Listable будет автоматически пронизывать любые аргументы списка:

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]}

Итак, поскольку f является Times , если вместо этого мы имеем пары чисел как два отдельных массива:

In[6]:= a1 = RandomInteger[{1, 100}, 10^6];
        a2 = RandomInteger[{1, 100}, 10^6];

In[7]:= First[Timing[a1*a2;]]
Out[7]= 0.012661

Wow , довольно быстро! Даже если вход не был представлен как два отдельных массива (или у вас есть более двух элементов в каждой паре), мы все равно можем сделать что-то оптимальное:

In[8]:= First[Timing[[email protected]@Transpose[array];]]
Out[8]= 0.020391

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

ответ дан Michael Pilat 07.03.2010 в 00:32
источник
  • Красиво сказал. Спасибо за этот вклад. Примером того, где я нахожу ForEach полезным, является создание хеш-таблицы. Скажем, у меня есть список структур данных; то я мог бы пройти через них, скажем, ForEach [{i_, {j_, k_}}, listofstuff, hash [f [i, k] = g [j, k]]. –  dreeves 07.03.2010 в 01:41
7

Встроенный 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.        *)
    
ответ дан dreeves 13.01.2011 в 23:25
источник
  • Даниэль, вам не нужно Evaluate здесь, а также ReleaseHold / Hold. Извините за то, что вы повторяетесь, но похоже, что вы не большой поклонник дополнительной набрав. Оценка не нужна, так как Cases не содержат аргументов, а HoldAll for ForEach гарантирует сохранение аргументов внутри ForEach. Случаи инициируют новый цикл оценки, на который этот HoldAll не влияет. ReleaseHold @ Hold [] вообще отсутствует (как я уже упоминал в другом месте), и здесь, в частности, поскольку SetDelayed не оценивает его r.h.s. На самом деле, вы можете захотеть наоборот: оберните свой первый в Unevaluated - имеет больше смысла для меня –  Leonid Shifrin 14.01.2011 в 01:01
  • @Leonid: Звучит правдоподобно, хотя я чувствую, что есть какая-то причина - возможно, тонкая ошибка - я закончил тем, что добавил дополнительную оценку, удержание и многое другое. Хотите добавить свою упрощенную версию в качестве ответа, и я проверю ее и отметю ее, если я смогу убедить себя, что это лучше? Еще раз спасибо за помощь! –  dreeves 14.01.2011 в 01:08
  • Конечно, почему бы и нет. Я гораздо менее заинтересован в получении кредита за это, чем с обеспечением того, чтобы неправильные представления не распространялись, поэтому я был бы счастлив, если (как только вы убедитесь, что это правильно), вы можете сделать это более заметным для других, посещающих эту страницу , какими бы то ни было средствами, не обязательно путем проверки его. Позвольте мне просто признать, что я довольно часто использовал ReleaseHold [Hold []] и Evaluate, пока я не понял оценку Mathematica намного лучше и понял, что они не нужны (по крайней мере, для подобных случаев). –  Leonid Shifrin 14.01.2011 в 01:33
3

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

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

или короткий

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

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

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

    
ответ дан kchoose2 23.11.2008 в 01:00
источник
  • Спасибо! Карта действительно почти всегда то, что вы хотите для таких вещей. Сканирование фактически идентично Карте, за исключением того, что оно используется строго для побочных эффектов - оно не возвращает список. –  dreeves 23.11.2008 в 07:14
2

Ниже приведено небольшое улучшение, основанное на последнем ответе трехэтажек, который позволяет указать шаблон без 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]
    
ответ дан faysou 31.08.2011 в 17:57
источник
1

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

Func /@ list

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

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

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

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

    
ответ дан Per Alexandersson 24.08.2009 в 18:48
источник
  • Спасибо, да, Карта (/ @), как правило, вы хотите. См. Мой комментарий к ответу, размещенному 23 ноября. –  dreeves 26.08.2009 в 07:25