Вопрос

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

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}

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top