Вопрос или проблема
В настоящее время я работаю над адаптацией таблицы Google Sheets к недавно созданной таблице Excel, которая динамически вычисляет вероятности для пулей кубиков, основанных на успехе (это моя одержимость, которая длится уже год). После небольших доработок я преобразовал все свои функции Lambda Excel в именованные функции Google Sheets, и, в целом, все созданные мной функции работают нормально.
Тем не менее, существует одна особенно сложная функция, которая кажется ограниченной пределами вычислений Google Sheets. Ссылка на таблицу Google здесь, и проблемная функция находится на вкладке “Exploding-Imploding Dice Pool”. Функция работает корректно, и если ее использовать на одной ячейке, проблем нет – она вычисляет и делает это относительно быстро! Логика функции, по-видимому, верна и дает ожидаемый результат.
Однако цель этой функции состоит в том, чтобы быть рабочей лошадкой для динамической таблицы, построенной с использованием MAKEARRAY() (которая зависит от COUNT()
двух массивов на выходе для получения своих размеров и MIN()
массива строк для вычисления его смещения), и, похоже, что если массив становится слишком большим, он вызывает эту ошибку:
Я предпринял некоторые шаги, чтобы попытаться минимизировать вычисления, и именованная функция DICEPOOLPROB_EXPIMP(num_sides, explode_target, success_target, botch_target, num_rerolls, num_dice, num_successes)
в данный момент закодирована следующим образом (с отступами, чтобы привести эту очень сложную функцию в порядок):
=IF(
num_rerolls<=0,
REDUCE(
0,SEQUENCE(MIN(num_dice,ABS(num_successes))+1,1,0,1),
LAMBDA(
ksum,k,
ksum+(((success_target-botch_target-1)/SQRT(num_sides^2-4*botch_target*(num_sides-explode_target+1)))^num_dice)
*IFERROR(COMBIN(num_dice,k),0)
*IFERROR(COMBINA(num_dice,ABS(num_successes-k)),0)
*((explode_target-success_target)/(success_target-botch_target-1))^k
*((2*IF(num_successes<0,botch_target,num_sides-explode_target+1))/(num_sides+SQRT(num_sides^2-4*botch_target*(num_sides-explode_target+1))))^ABS(num_successes-k)
*REDUCE(
0,SEQUENCE(num_dice,1,0,1),
LAMBDA(
jsum,j,
jsum+IFERROR(COMBIN(num_dice-1,j),0)
*(IFERROR(POCHHAMMER(num_dice,j),0)/IFERROR(POCHHAMMER(ABS(num_successes-k)+1,j),0))
*((num_sides-SQRT(num_sides^2-4*botch_target*(num_sides-explode_target+1)))/(2*SQRT(num_sides^2-4*botch_target*(num_sides-explode_target+1))))^j
)
)
)
),
REDUCE(
0,SEQUENCE(num_dice+1,1,0,1),
LAMBDA(
jsum,j,
jsum+IFERROR(
REDUCE(
0,SEQUENCE(j*(num_rerolls+1)+1,1,0,1),
LAMBDA(
ksum,k,
ksum+IFERROR(
REDUCE(
0,SEQUENCE(num_dice-j+1,1,0,1),
LAMBDA(
lsum,l,
lsum+IFERROR(
REDUCE(
0,SEQUENCE(INT((num_rerolls*(num_dice-j)-ABS(num_successes-j*(num_rerolls+1)+2*k-l))/2),1,0,1),
LAMBDA(
isum,i,
isum+IFERROR(
COMBIN(num_dice,j)
*COMBIN(j*(num_rerolls+1),k)
*COMBINL(num_dice-j,2*i+ABS(num_successes-j*(num_rerolls+1)+2*k-l),num_rerolls)
*COMBIN(2*i+ABS(num_successes-j*(num_rerolls+1)+2*k-l),i)
*((num_sides-explode_target+1)/num_sides)^(i+j*(num_rerolls+1)-k+MAX(0,num_successes-j*(num_rerolls+1)+2*k-l))
*(botch_target/num_sides)^(i+k-MIN(0,num_successes-j*(num_rerolls+1)+2*k-l))
*((explode_target-success_target)/num_sides)^l*((success_target-botch_target-1)/num_sides)^(num_dice-j-l),
0
)
)
)
,0
)
)
)
,0
)
)
)
,0
)
)
)
)
Эта функция воспроизводит следующее математическое уравнение, если num_rerolls
равно нулю или отрицательно:
И это более сложная функция, если num_rerolls
больше нуля:
Эта функция сама использует две другие именованные функции, COMBINL(number, number_chosen, choose_limit)
, которая вычисляет количество множеств с ограниченной емкостью контейнера, определена как:
=REDUCE(
0,SEQUENCE(MIN(number,INT(number_chosen/(choose_limit+1)))+1,1,0,1),
LAMBDA(
sum,i,
sum+(-1)^i
*COMBIN(number,i)
*COMBINA(number,number_chosen-i*(choose_limit+1))
)
)
и POCHHAMMER(n,k)
, стандартная математическая функция, которая определяется как:
=IFERROR(IF(
AND(INT(k)=k,INT(n)=n),
IF(
AND(n<=0,k<=-n),
((-1)^k*FACT(-n))/FACT(-n-k),
FACT(n+k-1)/FACT(n-1)
),
GAMMA(n+k)/GAMMA(n))
,0)
И ячейка MAKEARRAY()
, используемая для генерации (или не генерации) таблицы:
=MAKEARRAY(
COUNT(A16:A),COUNT(B15:15),
LAMBDA(
h,n,
IF(
AND(E4,h-1+MIN(A16:A)<>0,ABS(h-1+MIN(A16:A))<=n),
REDUCE(
0,SEQUENCE(ABS(h-1+MIN(A16:A)),1,h-1+MIN(A16:A),-SIGN(h-1+MIN(A16:A))),
LAMBDA(
jsum,j,
jsum+DICEPOOLPROB_EXPIMP(B2,B3,B4,B5,B6,n,j)
)
),
DICEPOOLPROB_EXPIMP(B2,B3,B4,B5,B6,n,h-1+MIN(A16:A))
)
)
)
(Функция IF()
предназначена для определения, является ли функция “кумулятивной” или нет, показывая вместо этого вероятность >=h
успехов, но это не имеет значения здесь, потому что я даже не начал тестировать этот MAKEARRAY()
в кумулятивном режиме – в данный момент мы смотрим только на вызов каждой ячейки MAKEARRAY()
функции DICEPOOLPROB_EXPIMP()
один раз.)
Как я уже упоминал, я уже пытался оптимизировать функцию и упростить степени, чтобы держать ее под контролем, но это на самом деле ничего не изменило – MAKEARRAY()
все равно ломается на 9 x 21 ячейках для неограниченной стороны функции (что плохо, но я могу с этим справиться), а ограниченная функция, похоже, работает только в одной ячейке – она, похоже, не позволяет MAKEARRAY()
выкрасть что-то, независимо от того, насколько мелким это оказывается.
Я обеспокоен тем, что проблема заключается в сильной зависимости функции от REDUCE()
и SEQUENCE()
для реализации циклов сумм, потому что я не уверен, есть ли лучшее функциональное решение, которое делать то, что мне нужно – я не могу просто использовать массивные формулы для чего-то столь сложного, во-первых, потому что массивы Google Sheets имеют только две размерности, а даже если бы они даже имели более высокие размерности, размеры сумм меняются по мере продвижения суммы – форма каждого цикла суммы зависит от состояния предыдущего цикла. REDUCE()
казался идеальной функцией для реализации этих циклов, и действительно, в Excel-версии этой таблицы это работает безупречно. Распределение вероятностей, которое я моделирую здесь, действительно имеет столько степеней свободы, я вполне уверен, что это наименее сложный вариант, по крайней мере, с точки зрения количества вложенных сумм. И я предполагаю, что тот факт, что COMBINL()
тоже использует REDUCE()
, может не улучшать ситуацию.
Итак, у меня два вопроса. Во-первых, известно ли кому-нибудь, какова фактическая предельная величина для вычислений в Google Sheets и как она рассчитывается? Я не смог найти никакой документации на эту тему. Во-вторых, есть ли у кого-нибудь лучшие идеи для оптимизации этой функции, чтобы либо уменьшить количество уровней вычислений, через которые она проходит, либо найти способ обойти предел вычислений? Я хотел бы, чтобы эта функция хотя бы работала на массиве 20 x 41 (20 кубиков, максимум 20 успехов, минимум -20 успехов).
Или, альтернативно, я просто лучше сделаю, повторно реализовав эти функции в качестве пользовательских функций JavaScript? Является ли это лучшим способом управления такой функцией?
Ответ или решение
К сожалению, Google Sheets имеет определенные ограничения на вычисления, которые могут вызывать ошибки, подобные той, с которой вы столкнулись. Прежде всего, давайте рассмотрим, как оптимизировать вашу названную функцию и обойти ограничение на вычисления.
1. Понимание ограничения вычислений в Google Sheets
Google Sheets имеет ряд ограничений, которые могут варьироваться в зависимости от типа формулы и сложности расчетов. На текущий момент документации по точным пределам отсутствует, но общепринято, что результаты многих формул влияют на производительность и могут вызывать ошибки, когда количество необходимых вычислений превышает определённый порог.
В частности, функции, которые используют рекурсивные вызовы, циклы и сложные вложенные конструкции (например, REDUCE()
и SEQUENCE()
), могут превышать лимиты Google, особенно при работе с большими массивами данных.
2. Оптимизация функции
Сокращение вложенности: Попробуйте уменьшить количество уровней вложенности в вашей функции. Чем сложнее структура, тем больше вычислений требуется. Возможно, стоит рассмотреть возможность разделения логики на несколько более простых функций.
Использование промежуточных расчетов: Если это возможно, проводите основные вычисления отдельно и сохраняйте их в промежуточных ячейках. Это позволит минимизировать повторные вычисления в основной функции.
Проверка логики и условий: Пересмотрите все условия, чтобы убедиться, что они действительно необходимы. Удаление излишних условий может помочь уменьшить нагрузку на вычисления.
3. Использование JavaScript в качестве альтернативы
Если после оптимизации функции она все еще не соответствует вашим требованиям, рассмотрите возможность создания пользовательского скрипта на JavaScript через Google Apps Script. Это позволяет создавать сложные функции, которые могут обрабатывать большие объемы данных, не сталкиваясь с ограничениями Google Sheets.
Для реализации этого подхода:
- Откройте Google Sheets и перейдите в
Extensions > Apps Script
. - Создайте новый проект и используйте JavaScript для программирования необходимой логики.
- Сохраните и разверните функцию, чтобы использовать её в своем документе Sheets.
Пример JavaScript функции
function DICEPOOLPROB_EXPIMP(num_sides, explode_target, success_target, botch_target, num_rerolls, num_dice, num_successes) {
// Ваша логика расчета здесь
}
4. Заключение
Работа с такими сложными функциями в Google Sheets может быть вызовом из-за их ограничений. Постарайтесь оптимизировать существующую логику или, в случае необходимости, переходите на JavaScript через Google Apps Script для более сложных расчетов. Это обеспечит максимальную гибкость и мощность вашей модели, позволяя избежать проблем с ограничениями на вычисление.
Если вам нужна помощь с конкретными аспектами кода или переключением на Google Apps Script, не стесняйтесь задавать дополнительные вопросы!