Я уже несколько лет опаздываю на вечеринку, и это, возможно, скорее ответ на «мета-вопрос», но то, с чем многие люди поначалу сталкиваются с трудностями, когда приближается программирование в 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, но в том, что вы часто можете получить те же результаты более эффективно и более элегантно, работая в функциональном мышлении, а не структурный.