Подтвердить что ты не робот

Использование функций массива и таблиц в Mathematica. Что лучше, когда

Я был главным пользователем функции функций таблицы в математике. Однако я заметил, что в нескольких примерах, где я использовал Array вместо Table для выражения того же результата, он работал значительно быстрее, особенно по мере увеличения размера таблицы.

Итак, мой вопрос заключается в следующем: когда скорость выполнения является основной задачей, когда наиболее целесообразно использовать таблицу?

Чем объясняется эта разница?

Я предполагаю, что поскольку массивы предполагают функциональную взаимосвязь между элементами в списке, они сохраняют их более эффективно, поэтому используют меньше памяти, что облегчает хранение и последующую обработку?

Это то, что происходит?

4b9b3361

Ответ 1

Array не имеет преимуществ по сравнению с Table. Между ними существуют различия, которые делают одно предпочтительным над другим.


EDIT Несколько человек отметили, что Table медленнее на многомерных массивах. Все они использовали переменную для хранения размера таблицы. Table имеет атрибуты HoldAll и только автоматически оценивает внешнюю внешнюю привязку. Поскольку внутренние итераторы остаются неудовлетворительными, элемент таблицы не компилируется. Использование явных чисел или With с результатом автоматической компиляции:
In[2]:= With[{b = 10^4, c = 10^4},
 {[email protected](#[[1, 1]] &[ar = Array[(# + #2) &, {b, c}]]) , 
  [email protected](#[[1, 1]] &[ta = Table[(i + j), {i, b}, {j, c}]])}
 ]

Out[2]= {{4.93, 2}, {4.742, 2}}

In[3]:= Attributes[Table]

Out[3]= {HoldAll, Protected}


Array позволяет построить массив значений функций так же, как и Table. Они принимают разные аргументы. Array выполняет функцию:
In[34]:= Array[Function[{i, j}, a[i, j]], {3, 3}]

Out[34]= {{a[1, 1], a[1, 2], a[1, 3]}, {a[2, 1], a[2, 2], 
  a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

в то время как таблица принимает явный вид:

In[35]:= Table[a[i, j], {i, 3}, {j, 3}]

Out[35]= {{a[1, 1], a[1, 2], a[1, 3]}, {a[2, 1], a[2, 2], 
  a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

Array может перемещаться только по регулярным массивам, а Table может выполнять произвольные итерации по списку:

In[36]:= Table[a[i, j], {i, {2, 3, 5, 7, 11}}, {j, {13, 17, 19}}]

Out[36]= {{a[2, 13], a[2, 17], a[2, 19]}, {a[3, 13], a[3, 17], 
  a[3, 19]}, {a[5, 13], a[5, 17], a[5, 19]}, {a[7, 13], a[7, 17], 
  a[7, 19]}, {a[11, 13], a[11, 17], a[11, 19]}}

Иногда Array может быть более кратким. Сравните таблицу умножения:

In[37]:= Array[Times, {5, 5}]

Out[37]= {{1, 2, 3, 4, 5}, {2, 4, 6, 8, 10}, {3, 6, 9, 12, 15}, {4, 8,
   12, 16, 20}, {5, 10, 15, 20, 25}}

против

In[38]:= Table[i j, {i, 5}, {j, 5}]

Out[38]= {{1, 2, 3, 4, 5}, {2, 4, 6, 8, 10}, {3, 6, 9, 12, 15}, {4, 8,
   12, 16, 20}, {5, 10, 15, 20, 25}}

Array позволяет строить выражение с любой головой, а не только с списком:

In[39]:= Array[a, {3, 3}, {1, 1}, h]

Out[39]= h[h[a[1, 1], a[1, 2], a[1, 3]], h[a[2, 1], a[2, 2], a[2, 3]],
  h[a[3, 1], a[3, 2], a[3, 3]]]

По умолчанию голова h выбрана равной List, что приводит к созданию регулярного массива. Таблица не обладает такой гибкостью.

Ответ 2

Майкл Тротт в программировании (стр. 707-710) обращается к вопросу о различиях между Array и Table и утверждает, что поскольку Table имеет атрибут HoldAll, он вычисляет свой аргумент для каждого вызова, тогда как Array "насколько это возможно" вычисляет свой аргумент только в начале. Это может привести к различиям в поведении, а также скорости.

Attributes[Table]

{HoldAll, Protected}

Attributes[Array]

{Protected}

Майкл Тротт использует следующие примеры, чтобы проиллюстрировать разницу в скорости и поведении. Я беру их дословно из своей книги (диска). Надеюсь, что я не нарушаю никаких правил.

Remove[a, i, j];
a = 0;
Table[a = a + 1; ToExpression[StringJoin["a" <> ToString[a]]][i, j],
       {i, 3}, {j, 3}]

{{a1 [1, 1], a2 [1, 2], a3 [1, 3]}, {a4 [2, 1], a5 [2, 2] a6 [2, 3]}, {a7 [3, 1], a8 [3, 2], a9 [3, 3]}}

a = 0;
Array[a = a + 1; 
 ToExpression[StringJoin["a" <> ToString[a]]], {3, 3}] 

{{a1 [1, 1], a1 [1, 2], a1 [1, 3]}, {a1 [2, 1], a1 [2, 2], a1 [2, 3]}, {a1 [3, 1], a1 [3, 2], a1 [3, 3]}}

(Обратите внимание на разницу в поведении)

Чтобы проиллюстрировать эффект предварительной вычисления первого аргумента, он использует следующий пример (снова verbatim, p 709).

o[a = 0;
  Table[a = a + 1; 
   ToExpression[StringJoin["a" <> ToString[a]]][i, j],
         {i, 3}, {j, 3}], {2000}] // Timing
Do[a = 0;
  Array[a = a + 1; ToExpression[ StringJoin["a" <> ToString[a]]], 
                                            {3, 3}], {2000}] // Timing

{0.700173, Null}

{0.102587, Null}

(Я использую mma7 на Mac. Моя копия программирования использует v5.1. Возможно, это будет обновление)

Это не единственное различие между Array и Table, обсуждаемым в Программе, конечно.

Я рассматриваю другие ответы, мне будет интересно узнать, что другие думают об этом.

Ответ 3

Одна из причин Array может быть быстрее, так как она часто компилирует свой первый аргумент лучше.

В Mathematica 7:

In[1]:= SystemOptions[CompileOptions -> ArrayCompileLength]

Out[1]= {"CompileOptions" -> {"ArrayCompileLength" -> 250}}

и

In[2]:= SystemOptions[CompileOptions -> TableCompileLength]

Out[2]= {"CompileOptions" -> {"TableCompileLength" -> 250}}

Итак, можно заключить, что Array и Table должны компилироваться в одной и той же точке.

Но попробуй. Я буду использовать функцию Timo timeAvg:

n = 15;
Array[Mod[#^2, 5]*(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]*(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.00034496 *)

(* Out = 0.00030016 *)

n = 16;
Array[Mod[#^2, 5]*(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]*(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.000060032 *)

(* Out = 0.0005008   *)

Мы видим, что Array способен скомпилировать Mod[#^2, 5]*(1 + #2) &, а Table не может скомпилировать Mod[i^2, 5]*(1 + j), и поэтому для Array становится быстрее при достижении CompileLength. Многие функции не столь благоприятны. Если вы просто изменяете умножение на деление в функции, что приводит к результату рационального, а не целого, тогда этот автокомпилировать не происходит, а Table выполняется быстрее:

n = 15;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.000576   *)

(* Out = 0.00042496 *)

n = 16;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.0005744  *)

(* Out = 0.0004352  *)

Но что, если мы сможем сделать этот компилятор тоже? Если мы используем числа с плавающей запятой, начиная с 1., мы получаем вывод Real, который может быть скомпилирован:

n = 15;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}, 1.] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, 1., n}, {j, 1., n}] // timeAvg

(* Out = 0.0006256  *)

(* Out = 0.00047488 *)

n = 16;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}, 1.] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, 1., n}, {j, 1., n}] // timeAvg

(* Out = 0.00010528 *)

(* Out = 0.00053472 *)

И снова Array быстрее в массиве больших размеров.

Ответ 4

Ваше выражение:

Однако я заметил, что в нескольких примерах, где я использовал Array вместо Table, чтобы выразить тот же результат, он работал значительно быстрее, особенно по мере увеличения размера таблицы.

обычно не является истинным для одномерных массивов. Посмотрите:

Cl;
Needs["PlotLegends`"]
$HistoryLength = 0;
a = 10^8;
arr = {}; tab = {};
Do[(
   arr = {[email protected]@Array[# &, a], arr};
   tab = {[email protected]@Table[i, {i, a}], tab};
   ), {10}];

ListLinePlot[{[email protected], [email protected]}, 
 AxesLabel -> {Style["Iteration", 14], Style["Time", 14]}, 
 PlotLegend -> {Style["Array", 14], Style["Table", 14]}, 
 PlotRange -> {{0, 10}, {1.6, 2}}]  

enter image description here

Ответ 5

Не указывая некоторые примеры, трудно ответить на ваш вопрос правильно.

Поскольку Mathematica является программой с закрытым исходным кодом, точные реализации за Table и Array не могут быть известны, если только они не объясняются людьми, участвовавшими в разработке Mathematica.

Эти факторы будут препятствовать вашей способности рассуждать, какую структуру вы должны использовать при каких обстоятельствах. Эксперимент будет вашим лучшим выбором, поэтому я предлагаю вам поддерживать параллельные версии ваших программ, используя Table, используя Array. Таким образом, вы сможете увидеть, какой из них быстрее.

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