I have some code that is hitting the 5 second barrier;

function: target N:n of A:s B:s C:s {
    result: [count {1..N, 1..(N/2)} in [sort {A, B, C}]]
output [target 7 of 4d12 0d20 0d8]
output [target 7 of 4d12 2d20 0d8]
output [target 7 of 4d12 4d20 0d8]

Even if I remove the final output line, it still fails.

I believe the code does what I want it to - calculate the number of dice rolling at or under the target from mixed pools, doubled if equal to or under half the target (it runs when using other pools: d20s seem to be a problem).

Is there anyway I can improve it so that at least the first two of these output lines will run (or better yet, all three of them)?

N.b. from my perspective these were some of the simplest pools I wanted to look at.


1 Answer 1


Yes, you can make the code more efficient by deferring casting your dice pools to sequences

Your big issue is that when you cast your dice pools to sequences at the function invocation, you're effectively invoking that function once for every possible permutation of the dice pool rolled, which gets to be infeasibly large numbers for Anydice quite quickly. However, it's not actually necessary for you to cast your dice to sequences at this stage.

You're not actually doing any complex inspection of the dice pool, so you don't need to fix the pools at this point in time and can just keep passing them through your function as a dice pool. The count X in Y builtin function will handle converting them to sequences instead. I'm not sure why your existing code includes a sort concatenating your dice pools - it's not necessary to sort the sequences before counting them, and if you get rid of it and just use count on each dice pool individually and sum the counts the result is the same.

Here's a function which handles all your example cases without issue:

function: target N:n of A:d B:d C:d {
    result: [count {1..N, 1..(N/2)} in A] + [count {1..N, 1..(N/2)} in B] + [count {1..N, 1..(N/2)} in C]

However, it will still choke once you get to 6 or 7 dice per pool.

But we can make it even more efficient with custom dice!

The computation time issue arises when you ask Anydice to iterate over the possible permutations of a long sequence, but in this case we can arrive at the same results via a more efficient method: defining custom dice.

As an example, if you want to count the number of dice in a d12 pool that roll 7 or less, that's the same as rolling a pool of custom 12-siders that just have a 1 on 7 faces and 0 on the other 5. Including double-counting for those that roll half the TN, that'd be a die with 2 on 3 faces, 1 on 4 faces, and 0 on the rest. You could define that manually in Anydice by declaring a sequence which matched and using that as a die:

A: {2:3,1:4,0:5}
output 4dA

This will give you the exact same result as your more complicated function above, but it's dramatically faster to compute since you're just summing dice, not doing any sequence inspection, and Anydice is really fast and good at summing dice.

To save our own time and prevent having to manually define such a die for every case, we can use a little function to make these custom dice before we use them in output statements. If you define a function to take a number and give it a dice expression, the result you get is itself a die representing the possible result distribution:

function: mkdie D:n TN:n {
  result: (D<=TN) + (D<=(TN/2))
TN: 7
A: [mkdie 1d12 TN]
B: [mkdie 1d20 TN]
C: [mkdie 1d8 TN]

output 4dA
output 4dA + 2dB
output 4dA + 4dB

And using this method, Anydice can happily compute results for you for dice pools of literally hundreds of dice.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .