5
\$\begingroup\$

The following question provides a function that answers the question for a single kind of die; How do I count the number of duplicates in anydice?

I want to count how many duplicates there are in a mixed pool such as 3d12, 2d10, 1d8.

For example, if I rolled 12, 12, 3, 3, 3, 1 the result would be 5; because 5 of the dice have at least one other die that has rolled the same value.

\$\endgroup\$
1
  • \$\begingroup\$ There are some great answers here, but I should note that the results for this question are slightly odd; because both pairs and 2 pairs are much easier to get than 3 of a kind, 3 is a less likely result than either 2 or 4. \$\endgroup\$ – Isaac Dec 31 '20 at 11:49
5
\$\begingroup\$

For exact duplicates, the program given in Carcer's answer to the question you cite can be easily modified to take multiple dice pools:

function: dupes in A:s B:s C:s {
  DICE: [sort {A, B, C}]
  DUPES: 0
  loop X over {2..#DICE} {
    if (X-1)@DICE = X@DICE { DUPES: DUPES + 1 }
  }
  result: DUPES
}

output [dupes in 1d12 2d10 1d8]

Note that, like Carcer's original code, this function will return 3 for (12, 12, 3, 3, 3, 1), not 5, because it counts a group of \$n\$ identical dice as \$n-1\$ duplicates! If you do want to count all dice that match at least one other die, here's a version that will do that:

function: dupes in A:s B:s C:s {
  DICE: [sort {A, B, C}]
  DUPES: 0
  loop X over {1..#DICE} {
    PREV_MATCH: X > 1 & (X-1)@DICE = X@DICE
    NEXT_MATCH: X < #DICE & (X+1)@DICE = X@DICE
    if PREV_MATCH | NEXT_MATCH { DUPES: DUPES + 1 }
  }
  result: DUPES
}

output [dupes in 1d12 2d10 1d8]

Note that this version will never yield a result of "one duplicate" — there are always at least two, or none at all!

Alas, because these programs need to iterate through all possible results of all the rolls, they can be rather slow if there are many dice with many sides in the pool. For example, my test outputs above use 1d12, 2d10 and 1d8, because trying to run them for your example of 3d12, 2d10 and 1d8 times out. :/

For bigger dice pools, my Python dice roller from an earlier answer can do the trick instead. Here's a solution using Carcer's counting method:

from collections import defaultdict
summary = defaultdict(float)

for d12, p12 in dice_roll(12, count=3):
  for d10, p10 in dice_roll(10, count=2):
    for d8, p8 in dice_roll(8, count=1):
      prob = p12 * p10 * p8
      roll = sorted(d12 + d10 + d8)
      dupes = sum(roll[i] == roll[i+1] for i in range(len(roll) - 1))
      summary[dupes] += prob

for dupes, prob in sorted(summary.items()):
  print("%d duplicates: %.2f%%" % (dupes, 100 * prob))

and here's one using yours:

from collections import defaultdict
summary = defaultdict(float)

for d12, p12 in dice_roll(12, count=3):
  for d10, p10 in dice_roll(10, count=2):
    for d8, p8 in dice_roll(8, count=1):
      prob = p12 * p10 * p8
      roll = sorted(d12 + d10 + d8)
      dupes = sum(
        (i > 0 and roll[i-1] == roll[i])
        or (i < len(roll)-1 and roll[i+1] == roll[i])
        for i in range(len(roll))
      )
      summary[dupes] += prob

for dupes, prob in sorted(summary.items()):
  print("%d duplicates: %.2f%%" % (dupes, 100 * prob))

(These are also brute force solutions, but Python is faster than AnyDice. And, if you run it on your own computer, it has no time limit.)


All of these programs can be fairly easily modified to also count "near misses". Since you've now split that part into a separate question, I've answered it there.

\$\endgroup\$
1
4
\$\begingroup\$

How to work with mixed dice pools in Anydice

In general to work with dice pools in Anydice we pass it to a function as a sequence (eg DICE:s) however you can't directly pass mixed pools that way (they get added before being passed which is very different). Instead you need to pass it multiple single-type pools and then pool them.

function: do a thing to A:s B:s C: {
   DICE: [sort {A, B, C}]
}

To explain what we're doing there; we're combining the sequences and then sorting it so that they numbers are in descending order the same way "normal" dice pools are in Anydice (which is made use of in a lot of situations). We're obviously coding in the max number of different dice, but extending it should be trivial and if you need fewer you can just pass it an empty sequence ({}).

Doing this as an extension to Carcer's function in the linked question we get:

function: dupes in mixed A:s B:s C:s {
  DICE: [sort {A, B, C}]
  D: 0
  loop X over {2..#DICE} {
    if ((X-1)@DICE = X@DICE) { D: D + 1}
  }
  result: D
}

However, this is a different function to what you're looking for (it would give 3 with your example), so for that we can instead use:

function: count dupes upto MAX in A:s B:s C:s {
   POOL: [sort {A, B, C}]
   R: 0
   loop N over {1..MAX} {
      T: [count N in POOL]
      if T > 1 { R: R + T }
   }
   result: R
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.