Comparing the Sum of expanding deck sizes and increasing draws per replicates in decks (using python 3.10)

Question:

So here are the rules

  1. The base deck is [2,3,4,5,6,7,8,9,10] which we can call Deck Size 1
  2. For each increase in Deck Size the deck is extend to include a replicated copy.
  3. each deck is extend [7,8,9,10] after the calculated size of the deck
  4. Deck size 2 would be
    [2,3,4,5,6,7,8,9,10,2,3,4,5,6,7,8,9,10,7,8,9,10] and for size 3
    [2,3,4,5,6,7,8,9,10,2,3,4,5,6,7,8,9,10,2,3,4,5,6,7,8,9,10,7,8,9,10] and so on.
  5. For example with deck size 3, the result of the play would be the sum of the draw, where the number of draws is equal to the deck size. Deck size 3 would be sum(draw1, draw2,draw3)

I feel confident that this will give the correct deck sizes

import itertools

# define base deck
base_deck = [2, 3, 4, 5, 6, 7, 8, 9, 10]

# define deck sizes
deck_a_size = 2
deck_b_size = 3

# create decks
deck_a_cards = list(itertools.chain.from_iterable(itertools.repeat(base_deck, deck_a_size)))
deck_a_cards.extend(floater)
print(deck_a_cards)
deck_b_cards = list(itertools.chain.from_iterable(itertools.repeat(base_deck, deck_b_size)))
deck_a_cards.extend(floater)
print(deck_b_cards)

However, I am not sure if this will calculate the correct probabilities

for and a, b

import itertools

# define base deck
base_deck = [2, 3, 4, 5, 6, 7, 8, 9, 10]

# define deck sizes
deck_a_size = 1
deck_b_size = 3

# create decks
deck_a_cards = list(itertools.chain.from_iterable(itertools.repeat(base_deck, deck_a_size)))
deck_a_cards.extend(floater)
print(deck_a_cards)
deck_b_cards = list(itertools.chain.from_iterable(itertools.repeat(base_deck, deck_b_size)))
deck_b_cards.extend(floater)
print(deck_b_cards)

deck_a_success = 0
deck_b_success = 0
total_combinations = 0

for i in itertools.product(deck_a_cards, repeat=deck_a_size):
    for j in itertools.product(deck_b_cards, repeat=deck_b_size):
        deck_a_sum = sum(i)
        deck_b_sum = sum(j)
        if deck_a_sum > deck_b_sum:
            deck_a_success += 1
        elif deck_a_sum < deck_b_sum:
            deck_b_success += 1
        else:
            deck_b_success += 1
        total_combinations += 1

deck_a_prob = deck_a_success / total_combinations
deck_b_prob = deck_b_success / total_combinations

print("Probability of success with {} cards drawn:".format(deck_a_size + deck_b_size))
print("Deck A:", deck_a_prob)
print("Deck B:", deck_b_prob)

And finally there has to be a better way to do this, because this is horrendously poor time complexity.

Ok I have now also tried with Monte-Carlo simulations

for both size 2,3 and 1,3 I get the very similar values:

import random

base_deck = [2, 3, 4, 5, 6, 7, 8, 9, 10]
deck_a_size = 2
deck_b_size = 3

num_trials = 1000000
deck_a_wins = 0
deck_b_wins = 0

for i in range(num_trials):
    deck_a_sum = sum(random.sample(base_deck, deck_a_size))
    deck_b_sum = sum(random.sample(base_deck * deck_b_size, deck_b_size))
    if deck_a_sum > deck_b_sum:
        deck_a_wins += 1
    elif deck_b_sum > deck_a_sum:
        deck_b_wins += 1

deck_a_prob = deck_a_wins / num_trials
deck_b_prob = deck_b_wins / num_trials

print("Probability of success with {} cards drawn:".format(deck_a_size+deck_b_size))
print("Deck A:", deck_a_prob)
print("Deck B:", deck_b_prob)

Probability of success with 5 cards drawn:
Deck A: 0.1316926655146557
Deck B: 0.8683073344853444
Run time 6.0s

With Monte-Calro

Probability of success with 5 cards drawn:
Deck A: 0.121357
Deck B: 0.837629
Runtime 3.8s

##resolved

Asked By: Thomas J Childers

||

Answers:

You results seem to be correct.

Since your code is using product (not permutations or combinations) I assume that when multiple draws are made, the drawn card is placed back in the deck before the next draw. This allows a simplification of the process by building a list of probabilities for each possible sum of a given deck size.

Based on these probabilities, each possible sum from one deck can be compared to the ones of the other deck and yield an outcome probability that we can then sum up to get the final result:

from itertools import product

floaters = [7,8,9,10]
baseDeck = [2, 3, 4, 5, 6, 7, 8, 9, 10]
Bs   = len(baseDeck)
Fs   = len(floaters)

def getSumProb(size):
    sump = prob = {n:(size+(n in floaters))/(size*Bs+Fs) for n in baseDeck}
    for _ in range(1,size):
        sump,prev = dict(),sump
        for (s,ps),(n,pn) in product(prev.items(),prob.items()):
            sump[s+n] = sump.get(s+n,0) + ps * pn
    return sump

The getSumProb function return a dictionary with possible sums as keys and their corresponding probabilities as value.

It first computes the probability of each individual number taking into account the repetitions and increased chance of the floater values. It then "folds" this list of individual probabilities over themselves to get sum values (for the multiple draws) and their respective probabilities (i.e. sum of probability products that form final sums).

To compute the probability of deckA draws winning over deckB’s, we simply add up the probabilities of each possible sum of deckA draw multiplied by the probabilities of deckB sums that are inferior.

output:

sumsA  = getSumProb(2)
sumsB  = getSumProb(3)

winAprob = sum(sumsA[sA]*sumsB[sB] for sA,sB in product(sumsA,sumsB) if sA>sB)

winBprob = 1 - winAprob

print(winAprob) # 0.13169266551465567
print(winBprob) # 0.8683073344853444

# runs in 0.0002 sec. on my laptop

If you need to isolate the cases where the two values are equal, you can compute the probability of equal sums separately and deduce winBprob from that

equalProb = sum( pA*sumsB.get(sA,0) for sA,pA in sumsA.items() )

winBprob = 1 - winAprob - equalProb

print(equalProb)  # 0.04061934507371048
print(winBprob)   # 0.8276879894116339

Note that, if the draw process does not place back the drawn card in the deck before the next draw, the sum probabilities will need to be calculated differently. This, however, would only require a few changes to the getSumProb function.

Answered By: Alain T.