Idiomdrottning’s homepage

A new operator and data structure for adding dice

OK, so we know that if you want to know the probability of either (or both) .4 and .4, you do 1-(1-.4)×(1-.4) = .64.

Today I came up with another way of doing it: X ior Y = X×(1-Y)+Y. To me this looks more straightforward somehow. In the example of .4 and .4, it’s .4×.6+.4 = .64

I was very happy to make this new discovery! Maybe it’s already known, I don’t know, but I found it and I haven’t seen it anywhere else and I’m happy.

Then we got to talking about other weird probability corner cases. For example, let’s say you have ten-sided dice that have these sides: 0, 0, 0, 0, 0, 0, 1, 1, 1, 2 and you wanted to roll seven of those dice and add them together. What would the probability be of you getting at least three?

Well, earlier today I made a 10⁷ table of every possible throw, filtered it on the ones where the sum was three or higher, and counted up the list and divided it by 10⁷, and got 0.6944032. It took Ellen several minutes to complete this. Nothing impossible, but it definitely counted as doing it “brute force”.

Then a while later it struck me. Why not just invert the whole thing?

I could switch places between the index and the element, so that that same die is written as (6 3 1)

A normal six-sider is written as (0 1 1 1 1 1 1). Zero sides have a “0” on them, one side has a 1, one has a 2, one as a 3 etc.

And now that I had this simple notation, I could quickly find out what the combination of two dice into one bigger dice would look like.

First, I needed to use comprehensions and the full numeric tower, and I wanted to be able to normalize the dice, so that (6 3 1) turns into (.6 .3 .1), so it’s easier to use the outcomes for probability.

(use numbers srfi-42)
(define (normalize lis)
  (map (cute / <> (reduce + 0 lis)) lis))

Then the operator itself uses a temporary vector so that for every combination of two element, it can add their product to the sum of their indexes.

(define (dice-add d1 d2 )
  (let ((result (make-vector (sub1 (+ (length d1) (length d2))) 0)))
     (: a (index i) d1)
     (: b (index j) d2)
       (vector-set! result (+ i j) (+ (vector-ref result (+ i j)) (* a b)))))
    (normalize (vector->list result))))

For example, two d6:es added together is (0 0 1/36 1/18 1/12 1/9 5/36 1/6 5/36 1/9 1/12 1/18 1/36). There are zero ways to get a 1 or a 2 result, and there’s a 1/36 chance of getting a 2 or a 12 result.

And now I can do:

(define d '(6 3 1))
(reduce + 0 (drop (reduce dice-add '(1) (make-list 7 d)) 3))

and get 217001/312500. Which checks out with the 0.6944032 I got from before. But it returns instantly instead of the minutes the brute search check took.

I can look at some other interesting dice, this:

(define dF '(1 1 1))
(reduce dice-add '(1) (make-list 4 dF))

gives the familiar dF distribution (1/81 4/81 10/81 16/81 19/81 16/81 10/81 4/81 1/81) offset by 4 since lists can’t have negative indexes.