Introduction
I'm trying to create custom Google Sheets functions for tabletop rolls and rules, and I ran into what appears to be a technical limitation on part of Google Sheets new Named Functions feature, but I'm hoping to be wrong and hope the community here can recognize a mistake of mine or recommend a sensible workaround1.
For whatever reason, it appears that Google Sheets' new Named Functions do not accept output from other named functions as input.
I have made an example sheet with a demonstration as well as a testing zone that the community is free to mess around with.
The Problem
I am working with two named functions that I have created:
FUNCTION =ROLL_D10(count)
count: The number of dice rolls you want to roll. Default: 1
=ARRAYFORMULA(ROUNDUP(RANDARRAY(IF(AND(ISNUMBER(count),count=INT(count),count>=1),count,1),1)*10,0))
FUNCTION =ROLL_ADVANTAGE(rolls)
rolls: A singular cell or array of cells consisting of numerical rolls.
=IFERROR(LARGE(rolls,1),NA())
The goal is to be able to nest the named functions like so: =ROLL_ADVANTAGE(ROLL_D10(3))
The result should be to retrieve the highest value from a 3d10 roll. To those familiar with tabletop, this would be a double advantage roll.
What appears to be happening is that once created, the nested function will not recalculate unless the cell that contains the nested formula is changed. For example, if I change =ROLL_ADVANTAGE(ROLL_D10(3)) to =ROLL_ADVANTAGE(ROLL_D10(3))&"A", it will recalculate the ROLL_D10 function. It will not recalculate if any other On Change or Every Minute/Every Hour event is triggered.
Tests Performed
I have tested the following methods and achieved the following results. Again I invite users to see the example sheet for live examples.
r = Non-named ROLL_D10 function; R = Named ROLL_D10 function.
a = Non-named ADVANTAGE function; A = Named ADVANTAGE function.
:S = Output posted to the sheet prior to being passed as input.
:ƒ = Output passed directly as formula input.
.n = Non-named function; .N = Named function.
‘⇒’ Output passed from ROLL_D10 to ADVANTAGE function.
Tested Operation
Result
r:S …Raw formula
Success
R:S …Raw formula
Success
a:S …Raw formula
Success
A:S …Raw formula
Success
r:ƒ …Raw formula
Success
R:ƒ …Raw formula
Possibly does not work with ƒ.N ⇒ f.N
a:ƒ …Raw formula
Success
A:ƒ …Raw formula
Success
r:S ⇒ a
Success
r:S ⇒ A
Success
R:S ⇒ a
Success
R:S ⇒ A
Success
r:ƒ ⇒ a
Success
r:ƒ ⇒ A
Success
R:ƒ ⇒ a
Success
R:ƒ ⇒ A
FAILURE
Annotations
I consider a sensible workaround to be avoiding integrating advantage/disadvantage/neutral arguments into the roll functions as this increases complexity and therefore makes it less user-friendly: I do hope to make these functions available generally. If this is the only reasonable option, that is fine, but I would prefer to explore other options first.
In the formula =ROLL_ADVANTAGE(ROLL_D10(3)), the inner function ROLL_D10(3) has a single static parameter 3 that never changes. The function will not recalculate as long as the parameter remains the same. Since ROLL_D10(3) will always return the same result, the outer ROLL_ADVANTAGE() function will also return its cached result instead of recalculating.
This behavior is a side effect of the memoization used by Google Sheets to cache the return values of functions. Its purpose is to improve spreadsheet performance. As long as a function's parameters remain the same, it will return the cached result without recalculating it.
The standard recipe to make the function recalculate is to add a dummy parameter that does get changed. The signature becomes ROLL_D10(count, dummy). Then modify the formula to make the dummy parameter refer to a value that gets updated with new values every now and then, such as cell A1, like this:
=ROLL_ADVANTAGE(ROLL_D10(3, A1))
Your named function does not need to actually use the dummy parameter — its mere presence is enough to avoid the memoization effect. The named function will get called every time the value in cell A1 changes, which is not a perfect solution, but it is usually good enough for most needs. You can increment the value in cell A1 manually, or use =now() there to easily force recalculation from time to time.
Related
I have some expressions containing nested sums, looking like this:
and I want to substitute one of the summation indices(such as i369) for something else.
But the problem is, sometimes maxima seems to re-define the whole sum every time expr is called. So, every time I call second(expr) maxima tells me some new index names, instead of i369:
So when I call subst, nothing happens because it sees:
How to stop this from happening?
Thanks for the additional information. The changing indices are the result of sumexpand being true, and test3 is a product of summations, and it's getting resimplified according to sumexpand every time it is evaluated. The sumexpand transformation assigns new indices every time it processes a summation, so that's how you're getting different indices each time you evaluate test3.
I think the clearest way forward is to only enable sumexpand when you need to process a summation, and otherwise leave it disabled. Something like block([sumexpand: true], <whatever operations here>) enables sumexpand for the duration of the block and then restores the previous value. Something like that works for global flags in general.
As an aside, bear in mind that function definitions are always global. When you write f(x) := block(g(y) := ..., ...), the function g is not local to f; instead f and g are both globally defined. You can supply lambda expression to scanmap instead of a named function, or just pull the would-be local function out and define it outside of simplify_expr_for_extract.
I've built a function in a Google Sheets that takes pages of data and passes them through to pull metrics from the datasets (which are based on pivot tables of extracted data). All data is pulled programatically, formatted the same way, and named by standardized conventions. I want the standard deviation of all values in the set that are above 100 and in most cases the function I've built works:
=STDEV((IF(INDIRECT($A7&"!B:B")>100,INDIRECT($A7&"!B:B"),""))
However with some smaller datasets STDEV starts throwing errors representative of not being passed enough arguments. I've tried debugging by pulling out pieces, eliminating thresholds, and trying other varieties of STDEV (STDEVA gives me a DIV/0 error, STDEVP and STDEVPA return 0 as the standard deviation) and when I pull out that IF statement it looks like it's returning FALSE as though there's no data in the set that fits the criteria. Except, when I lower the thresholds to >0 or eliminate the threshold entirely it still doesn't work and I know that there are 4+ values in all erroring datasets that are >100. In fact, the same call is summing to non-zero in the column right next to it. What's more, the function works everywhere else but these datasets.
What gives?
For extra info here's a viewable link to the sheet:
https://docs.google.com/spreadsheets/d/1b_456W9UlkuIc6W_FjmFwgycY1xAUkD_W9aMSxG0N6o/edit?usp=sharing
And this is the error the STDEV is throwing:
"Function STDEV parameter 1 expects number values. But '' is a text and cannot be coerced to a number."
Halp
use:
=IFERROR(AVERAGEIF(INDIRECT($A15 & "!B:B"), ">100"))
and:
=IFERROR(STDEV(IF(INDIRECT($A15&"!B:B")>100, INDIRECT($A15&"!B:B"), )))
Whenever I try to perform a calculation with a large number result,
26^14
spreadsheets automatically rewrite it in scientific notation, not saving the excluded numbers and cutting it off.
6.451E+19
To get around this, I have to manually type in the answer.
64509974703297150976
This works for displaying the number, but whenever I try to multiply this number in another cell, sheets will perform the calculation on the rounded number form of the scientific notation number.
64509974703297200000 * 2 = 129019949406594000000
This is the number I get instead of the desired:
64509974703297150976 * 2 = 129019949406594301952
Exact numbers are required and this is just getting really frustrating. I would really like to have the answer to stop google sheets from doing this as soon as possible (duh).
Since Google Sheets use JavaScript, you may copy a library and use it.
Here is the link to library with description on how to use:
https://github.com/peterolson/BigInteger.js
Step 1
Go here and copy the code into your Script Editor:
https://github.com/peterolson/BigInteger.js/blob/master/BigInteger.min.js
Step 2
Write your own custom function like this:
function bigIntPow(a, b)
{
return bigInt(a).pow(b).toString();
}
Step 3
Use the function from Sheet:
=bigIntPow(26,14)
The result is text: 64509974703297150976
Is there a way to have PTransform B depend on PTransform A when A doesn’t produce any outputs? Or do I have to have A produce a dummy output that’s fed into B as a side input? An example use case is where I want to have the following pipeline:
Z = read file
A = count lines in file, and throw error if there are no lines
B = do something with the file
I want B to start only after A finishes, but A doesn’t produce any output PCollection useful to B.
It is possible, but may not really be desirable in your case. Adding a dependency like this will slow down the parallel execution of the program, since B will need to wait for A to complete before it can start.
If you really want to do this, the way you described -- outputting an element and using that as the side input to B should work. Consider instead the following, which allows you to use the primitive Count transform to implement A, and moves all the logic into one place:
Z = read file
A = count lines in file
B = side input from A, throw error if the count of lines was zero,
otherwise do something with the file
Setup by running sadd a b c
When I execute this code against the set a
keystoclear1 has a single value of "b" in it.
keystoclear2 as both values in it.
local keystoclear = unpack(redis.call('smembers', KEYS[1]))
redis.call('sadd', 'keystoclear1', keystoclear)
redis.call('sadd', 'keystoclear2', unpack(redis.call('smembers', KEYS[1])))
I am by no means a lua expert, so I could just have some strange behavior here, but I would like to know what is causing it.
I tested this on both the windows and linux version of redis, with redis-cli and the stackexchange.redis client. Same behavior in all cases. This is a trivial example, I actually would like to store the results of the unpack because I need to perform several operations with it.
UPDATE: I understand the issue.
table.unpack() only returns the first element
Lua always adjusts the number of results from a function to the circumstances of the call. When we call a function as a statement, Lua discards all of its results. When we use a call as an expression, Lua keeps only the first result. We get all results only when the call is the last (or the only) expression in a list of expressions.
This case is slightly different from the one you referenced in your update. In this case unpack (may) return several elements, but you only store one and discard the rest. You can get other elements if you use local keytoclear1, keytoclear2 = ..., but it's much easier to store the table itself and unpack it as needed:
local keystoclear = redis.call('smembers', KEYS[1])
redis.call('sadd', 'keystoclear1', unpack(keystoclear))
As long as unpack is the last parameter, you'll get all the elements that are present in the table being unpacked.