A programming exercise given to me as part of a job application.
Go to file
2024-04-19 13:04:55 +02:00
LICENSE First commit. 2024-04-19 12:57:47 +02:00
pydiceprob.py First commit. 2024-04-19 12:57:47 +02:00
Python_Programming_Interview_Problem_1.pdf First commit. 2024-04-19 12:57:47 +02:00
README.org TODO for unit tests. 2024-04-19 13:04:55 +02:00

Python Programming Dice Probabilities

Introduction

CLOCK: [2024-04-19 Fri 10:33][2024-04-19 Fri 12:50] => 2:17 CLOCK: [2024-04-17 Wed 13:40][2024-04-17 Wed 14:14] => 0:34

I was given a problem as such:

You have two players, Bob and Alice, that take turns in rolling a fair k-sided die. Whoever rolls a k first wins the game. The Python program should output the probability that Bob wins the game for k = 6 thru 99. That is, the output will be an array of probabilities where index 0 is the probability when k = 6; index 1 when k = 7; etc.

Bonus points:

If you have 8me and interest, create a REST server rather than a console program. Flask or FastAPI can be used. The REST endpoint should be a GET, accept no Request Body, accept an op8onal Header for “k”, and return:

  • The array of probabili8es in JSON format if no “k” is provided in the Header
  • A single probability in JSON format if a “k” is provided in the Header

TODOS:

  • Unit tests
  • REST API

Thinking

Given the problem's nature, we need the following functionality:

  1. Output the probability for Bob to win.
  2. Create an array of probabilities that contains the data for the output.
  3. Calculate the probabilities based on the value of k.
  4. Define the value of k, between 6 and 99 inclusive.

For the bonus secion:

  1. Create a FastAPI endpoint that takes an optional Header for k.
  2. Return either the full array of probabilities (no k provided), or return the specific probability if k is provided.

Code

The following is python code blocks, with the documentation attached. Using org-babel, these are tangled into pydiceprob.py, which is the final python script.

Imports

import sys
import json
import pprint
None

Pretty Print

pp = pp.Printer = pprint.PrettyPrinter(indent=2, compact=True)

And a usage printe

def usagequit():
    print("""Usage: python pydiceprob.py [k] [mode]
    k between 6 and 99
    modes: single, single-table, multi, multi-table
    single prints out the probability of a single throw with a k-sided dice yielding k.
    single-table prints out a table between 6 and k with the probabilities of a single throw yielding k.
    multi prints out the probability of winning (assuming we're going first) in a game where two players take turns and the person who throws k first wins.
    multi-table prints out the probability of winning for a range between 6 and k if you have the first throw.""")
    quit()

Main

Main function for managing CLI interactions, and dispatch

def main():
    global k
    try:
        if int(sys.argv[1]) >= 6:
            k = int(sys.argv[1])
        else:
            usagequit()
    except IndexError:
        usagequit()
    global mode
    try:
        mode = sys.argv[2]
    except IndexError:
        usagequit()
    dispatch(mode, k)
def dispatch(mode, k):
    modes = ["single", "multi", "single-table", "multi-table"]
    if mode not in modes:
        usagequit()
    if mode == "single":
        print(first_turn_probability(k))
    elif mode == "multi" :
        print(multi_turn_single(k))
    elif mode == "single-table":
        print(first_turn_protatbilities(k))
    elif mode == "multi-table":
        print(multi_turn_table(k))

Array of dice and players

Second, we need to grab the value of k, the size of the dice we're calculating the probability for. Depending on the implementation we choose to use, we'll be either grabbing it from the CLI, or the REST API. For now, this will remain in the CLI.

Then for the bonus code, we need several steps. First, we need to generate a list such that we have numbers between 6 and 99 in it.

def dice(k):
    if k < 6:
        print("Dice must be at least 6-sided")
    elif k == 6:
        dice_array = [6]
    else:
        dice_array = list(range(6, k))
None

Single turn win probability

Then, we have to calculate the probability of a win on each throw given a dice of size k.

Both players (Alice and Bob) throw dice, alternating, and the person who throws the top number first (k) wins. Bob always goes first for simplicity.

A dice of size k has a 1/k probability of winning each throw.

k+1 here, because python calculates range(a, b) indices in the way of b-a, so for a = 6 and b = 10, it'll give an array of 4 items (10-6 = 4) as such: [6, 7, 8, 9], as it counts the first item as 1.

Using k+1 we can get the correct, inclusive array that we desire: [6, 7, 8, 9, 10].

def first_turn_probabilities(k):
    result = {}
    for k in range(6, int(k)+1):
        result[k] = 1/k
    return result

def first_turn_probability(k):
    return first_turn_probabilities(k)[k]

Multi-turn win probability

Over many throws, the advantage of winning will decrease, approaching 50%.

We're assuming Bob goes first, and then is followed by Alice. For each throw, the probability of winning is the same.

def multi_turn_single(k):
    p_win = 1 / k  # Winning probability on any given throw
    p_lose = (k-1) / k   # Losing probability on any given throw
    bob_wins_prob_sum = 0
    r = p_lose**2
    probability_win = p_win / (1 - r)
    return probability_win

And then we want to generate a table of all the probabilities up to k.

def multi_turn_table(k):
    result = {}
    for i in range(6, int(k+1)):
        result[i] = multi_turn_single(i)
    return result

Print

def print(stuff):
    pp.pprint(stuff)

Script

To run as a script.

if __name__ == "__main__":
    main()