Arbitrary Dice with Probability I wrote a script that gives a "generalized" function of an arbitrary dice. Is this any good? How can it improve so to make it cleaner?

import random
def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Die1 = [1,2,3,4]
Die2 = [1,2,3,4,5,6] #num lists

def inptchacc():
    ending_conditions = ['stop','Stop','quit','Quit']
    end = False
    inpu = input('what number would you like to add to the new face of the Die? (to end the die please type "stop or Stop or quit or Quit to finish the DIE" )')
    while end == False:
        if not(inpu in ending_conditions):
            try:
                retr = int(inpu)
                return retr
            except:
                string = 'invalid input please try again'
                inpu = input('invalid input please try again ')
        else:
            stop = 'stop'
            return stop

def deeper(IDie):
    list = []
    Adding = True
    while Adding:
        print('The Die ' + IDie + ' is currently ' + str(list) )
        toadd = inptchacc()
        if toadd != 'stop':
            list.append(toadd)
        else:
            Adding = False
    return list


def chance_overlap(Die1,Die2):
    highnumber = 1000
    counter = 0
    for n in range(highnumber):
        x = roll(Die1)
        y = roll(Die2)
        if x == y:
            counter += 1
    chance = counter/highnumber
    return chance

chance = chance_overlap(Die1,Die2)

print(chance)
Doing = True
while Doing:
    try:
        IDie1 = deeper('IDie1')
        IDie2 = deeper('IDie1')
        chance2 = chance_overlap(IDie1,IDie2)
        Doing = False
    except:
        print ('incompatible options selected returning....... ')

print(chance2)
Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

from itertools import groupby

def roll(die, n=1):
    if n == 1:
        return random.choice(die)
    return random.choices(die, k=n)

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

def overlap_chance(*dice, n=1000):
    rolls = [roll(die, n) for die in dice]
    equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
    return equal_rolls / n


Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

In addition, I followed Python's official style-guide, PEP8 for variable names (lower_case).

The all_equal function is directly taken from the itertools recipes.



Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

Each distinct value \$j\$ on each die \$i\$ has probability \$p^i_j = n^i_j / k_i\$, where \$k_i\$ is the number of faces of die \$i\$ and \$n^i_j\$ the number of times the value \$j\$ appears on that die. Then the chance to have an overlap is simply given by

$$
P(overlap) = \sum\limits_j \prod\limits_i p^i_j = \sum\limits_j \prod\limits_i n^i_j / k_i,
$$

where \$i\$ goes over all dice and \$j\$ over all values present on any dice (with \$n^i_j = 0\$ if value \$j\$ does not appear on die \$i\$).

In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are \$P(HH \vee TT) = P_1(H)\cdot P_2(H) + P_1(T) \cdot P_2(T) = 0.5\cdot0.5 + 0.5\cdot0.5 = 0.5\$.

from collections import Counter
from functools import reduce
from itertools import chain
from operator import mul

def overlap_chance_real(*dice):
    all_values = set(chain.from_iterable(dice))
    counters = [Counter(die) for die in dice]
    lengths = [len(die) for die in dice]
    return sum(reduce(mul, [counter[val] / length
                            for counter, length in zip(counters, lengths)])
               for val in all_values)


The nice thing about this is that we don't need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

The execution time of the first function (overlap_chance)  increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):



With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both: