It looks like AnyDice just can't handle calculating "10k3" even with rerolls limited to just one. So I took my old dice probability calculator written in Python and added a few more features to it.
With the code from this GitHub gist saved as dice_roll.py
in the current directory, you can load it into the Python REPL with python -i dice_roll.py
and then e.g. calculate and print the distribution of 10k3 (with up to two rerolls by default) in CSV format like this:
exploded_d10 = explode(10, count=2)
for num, prob in sum_roll(exploded_d10, count=10, select=3, ascending=True):
print('%d, %.12g' % (num, 100*prob))
Or just Try It Online!
The results, for various numbers of allowed rerolls per die, look like this:
Looking at the graph, one can see that the first two rerolls make a noticeable difference, but the effect of the later ones is pretty negligible. Which makes sense: for each die, the probability of getting at least n rerolls is 1/10n, so the expected number of dice out of 10 that get 3 or more rerolls is 10/103 = 1/100. And since this expected number of third rerolls is much less than one, it's also approximately equal to the probability of getting even a single third reroll. And the expect number of fourth rerolls, of course, is only 1/1000, and so on.
The Python implementation I wrote handles this problem better than AnyDice for two reasons. The first is simply that it doesn't have the 5 second runtime limit of AnyDice, so (at least when running it locally on your own computer) you can let it run as long as it needs to.
The second reason is that my code is actually a bit smarter than AnyDice, and avoids generating all the possible combinations of the lowest 10 − 3 = 7 dice rolls just to throw them away. This means that, despite Python being a much slower language in general than C++ (which I believe AnyDice is written in), my program actually manages to calculate e.g. 10k3 with max 2 rerolls in only about 0.1 seconds on TIO, significantly faster than AnyDice (which times out).
The code in the gist is actually quite a flexible mini-framework, and can do pretty much anything AnyDice can do (albeit some things more easily than others) and a few things that AnyDice cannot. Some examples:
# basic dice rolls, exploding dice, drop lowest
d6 = make_simple_die(6) # d6
sum2d6 = sum_roll(d6, count=2) # (sum of) 2d6
exp2d6 = explode(sum2d6, count=2) # [explode 2d6]
output = sum_roll(exp2d6, count=3, select=2) # [highest 2 of 3d[explode 2d6]]
# statistics (just plain Python, but occasionally useful)
average = sum(n * p for n, p in output)
std_dev = sum((n - average)**2 * p for n, p in output)**0.5
# custom dice are tuples of (value, probability) pairs
dF = tuple((n, 1.0/3) for n in (1, 0, -1)) # fudge die
sum10dF = sum_roll(dF, count=10) # 10dF
# reverse input die to select lowest instead of highest rolls
rev_d6 = reversed(d6)
lowest = sum_roll(rev_d6, count=4, select=3) # [lowest 3 of 4d6]
# custom result manipulation example: probability of all dice in 5d6 being equal
yahtzee_prob = 0.0
for roll, prob in dice_roll(d6, count=5):
high = roll[0] # first element is highest (for normal input dice)
low = roll[-1] # last element is lowest
if high == low: yahtzee_prob += prob
# dice sides can actually be anything (that can be summed, if using sum_roll)
sqrt_d6 = tuple((n**0.5, p) for n, p in d6) # sqrt(d6)
sum_sqrt = sum_roll(sqrt_d6, count=3) # 3d(sqrt(d6))
abcdef = tuple((letter, 1.0/6) for letter in "ABCDEF")
triples = tuple(dice_roll(abcdef, count=3))
The code itself provides basic documentation on how to use the various functions it provides. FWIW, all named arguments in the examples above are optional (with fairly reasonable defaults) and can be either named or given as simple positional arguments, so e.g. sum_roll(d6)
and sum_roll(d6, 1, 1)
are both equivalent to sum_roll(d6, count=1, select=1)
.
FWIW, this is getting kind of close to something resembling a reimplementation of AnyDice in Python. I really should consider turning it into a proper Python module with decent documentation.