Предлагаемое решение
Вот версия Table
, которая Abort
-поддерживается и будет сохранять промежуточные результаты, собранные до сих пор. Это модифицированная версия решения, размещенная здесь .
ClearAll[abortableTable];
SetAttributes[abortableTable, HoldAll];
abortableTable[expr_, iter__List] :=
Module[{indices, indexedRes, sowTag},
SetDelayed @@
Prepend[Thread[Map[Take[#, 1] &, List @@ Hold @@@ Hold[iter]],
Hold], indices];
indexedRes =
If[# === {}, #, First@#] &@Last@Reap[
CheckAbort[Do[Sow[{expr, indices}, sowTag], iter], {}], sowTag];
AbortProtect[
Map[First,
SplitBy[indexedRes,
Table[
With[{i = i}, Function[Slot[1][[2, i]]]],
{i, Length[Hold[iter]] - 1}]],
{-3}]]];
Должно быть в состоянии принять ту же спецификацию итератора, что и Table
.
Как это работает
Вот как это работает. Первый оператор (SetDelayed @@...
) «анализирует» итераторы, предполагая, что они имеют форму {iteratorSymbol_,bounds__}
, и назначает список переменных итератора переменной indices
. Конструкция с Hold
необходима для предотвращения возможной оценки переменных итератора. Есть много способов сделать это, я использовал только один из них. Вот как это работает:
In[44]:=
{i, j, k} = {1, 2, 3};
Prepend[Thread[Map[Take[#, 1] &, List @@ Hold @@@
Hold[{i, 1, 10}, {j, 1, 5}, {k, 1, 3}]], Hold], indices]
Out[45]= Hold[indices, {i, j, k}]
Использование SetDelayed @@ the-above
естественным образом приведет к отложенному определению формы indices:={i,j,k}
. Я присвоил значения индексам i,j,k
, чтобы продемонстрировать, что при использовании этой конструкции нежелательной их оценки не происходит.
Следующий оператор создает список собранных результатов, где каждый результат группируется в списке со списком индексов, использованных для его получения. Поскольку переменная indices
определяется отложенным определением, она будет вычисляться каждый раз заново для новой комбинации индексов. Другая важная особенность, используемая здесь, заключается в том, что цикл Do
принимает тот же синтаксис итератора, что и Table
(а также динамически локализует переменные итератора), в то время как является последовательной (постоянной памятью) конструкцией. Для сбора промежуточных результатов были использованы Reap
и Sow
. Поскольку expr
может быть любым фрагментом кода и, в частности, может также использовать Sow
, пользовательский тег с уникальным именем необходим только для Reap
тех значений, которые были Sown
нашей функцией, но не кода это выполняется. Поскольку Module
естественно создает (временные) символы с уникальным именем, я просто использовал сгенерированную Module
переменную без значения в качестве тега. Это в целом полезная техника.
Чтобы иметь возможность собирать результаты в случае Abort[]
, выданного пользователем в интерактивном режиме или в коде, мы заключаем цикл Do
в CheckAbort
. Код, который выполняется в Abort[]
({}
здесь), в этом подходе в значительной степени произвольный, поскольку сбор результатов в любом случае выполняется Sow
и Reap
, хотя может быть полезен в более сложной версии, которая сохраните результат в некоторую переменную, предоставленную пользователем, а затем повторно введите Abort[]
(функциональность, в настоящее время не реализованная).
В результате мы получаем в переменную indexedRes
плоский список вида
{{expr1, {ind11,ind21,...indn1}},...,{exprk, {ind1k,ind2k,...indnk}}
, где результаты сгруппированы с соответствующей комбинацией индексов. Нам нужны эти комбинации индексов для восстановления многомерного результирующего списка из плоского списка. Способ сделать это состоит в том, чтобы повторно разделить список в соответствии со значением i
-го индекса. Функция SplitBy
имеет эту функцию, но нам нужно предоставить список функций, которые будут использоваться для разделения шагов. Поскольку индекс i
-го итератора в подсписке {expr,{ind1,...,indn}}
равен 2,i
, функция, которая выполняет разбиение на i
-м шаге, равна #[[2, i]]&
, и нам нужно составить список таких функций динамически кормить его до SplitBy
. Вот пример:
In[46]:= Table[With[{i = i}, Function[Slot[1][[2, i]]]], {i, 5}]
Out[46]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[2, 5]] &}
Конструкция With[{i=i},body]
использовалась для введения определенных значений i
внутри чистых функций. Альтернативы для ввода значения i
в Function
существуют, например, например ::1010*
In[75]:=
Function[Slot[1][[2, i]]] /. Map[List, Thread[HoldPattern[i] -> Range[5]]]
Out[75]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[2, 5]] &}
или
In[80]:= Block[{Part}, Function /@ Thread[Slot[1][[2, Range[5]]]]]
Out[80]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[ 2, 5]] &}
или
In[86]:= Replace[Table[{2, i}, {i, 5}], {inds__} :> (#[[inds]] &), 1]
Out[86]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[ 2, 5]] &}
но, вероятно, еще более неясны (возможно, кроме последнего).
Полученный вложенный список имеет правильную структуру с подсписками {expr,{ind1,...,indn}}
, находящимися на уровне -3
(третий уровень снизу).Используя Map[First,lst,{-3}]
, мы удаляем комбинации индексов, поскольку вложенный список уже восстановлен и они больше не нужны.Что остается, так это наш результат - вложенный список результирующих выражений, структура которого соответствует структуре аналогичного вложенного списка, созданного Table
.Последний оператор заключен в AbortProtect
- на всякий случай, чтобы убедиться, что результат возвращается до возможного срабатывания Abort[]
.
Пример использования
Вот пример, где я нажал Alt+.
(Abort[]
) вскоре после оценки команды:
In[133]:= abortableTable[N[(1+1/i)^i],{i,20000}]//Short
Out[133]//Short= {2.,2.25,2.37037,2.44141,<<6496>>,2.71807,2.71807,2.71807}
Это почти так же быстроas Table
:
In[132]:= abortableTable[N[(1+1/i)^i,20],{i,10000}]//Short//Timing
Out[132]= {1.515,{2.0000000000000000000,2.2500000000000000000,<<9997>>,2.7181459268252248640}}
In[131]:= Table[N[(1+1/i)^i,20],{i,10000}]//Short//Timing
Out[131]= {1.5,{2.0000000000000000000,2.2500000000000000000,<<9997>>,2.7181459268252248640}}
Но он не компилируется автоматически, в то время как Table
делает:
In[134]:= Table[N[(1+1/i)^i],{i,10000}]//Short//Timing
Out[134]= {0.,{2.,2.25,2.37037,2.44141,<<9993>>,2.71815,2.71815,2.71815}}
Можно кодировать автокомпиляцию и добавить ее к вышеупомянутому решению.Я просто не делал этого, потому что это будет много работы, чтобы сделать это правильно.
РЕДАКТИРОВАТЬ
Я переписал функцию, чтобы сделать некоторые части более краткимии легче понять.Кроме того, это примерно на 25% быстрее, чем в первой версии, в больших списках.
ClearAll[abortableTableAlt];
SetAttributes[abortableTableAlt, HoldAll];
abortableTableAlt[expr_, iter : {_Symbol, __} ..] :=
Module[{indices, indexedRes, sowTag, depth = Length[Hold[iter]] - 1},
Hold[iter] /. {sym_Symbol, __} :> sym /. Hold[syms__] :> (indices := {syms});
indexedRes = Replace[#, {x_} :> x] &@ Last@Reap[
CheckAbort[Do[Sow[{expr, indices}, sowTag], iter], Null],sowTag];
AbortProtect[
SplitBy[indexedRes, Array[Function[x, #[[2, x]] &], {depth}]][[##,1]] & @@
Table[All, {depth + 1}]
]];