First commit.

This commit is contained in:
Phil Bajsicki 2024-04-19 12:57:47 +02:00
parent 69535e749e
commit ee032555eb
5 changed files with 279 additions and 5 deletions

View file

@ -209,7 +209,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
python-dice-top-k-game-probabilities python-dice-top-k-game-probabilities
Copyright (C) 2024 phil Copyright (C) 2024 Phil Bajsicki
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
python-dice-top-k-game-probabilities Copyright (C) 2024 phil python-dice-top-k-game-probabilities Copyright (C) 2024 Phil Bajsicki
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

Binary file not shown.

View file

@ -1,3 +0,0 @@
# python-dice-top-k-game-probabilities
A programming exercise given to me as part of a job application.

196
README.org Normal file
View file

@ -0,0 +1,196 @@
#+title: Python Programming Dice Probabilities
#+auto_tangle: t
#+PROPERTY: session *python*
#+PROPERTY: cache yes
#+PROPERTY: exports both
#+PROPERTY: header-args :tangle pydiceprob.py
* Introduction
:LOGBOOK:
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
:END:
I was given a problem as such:
#+begin_quote
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.
#+end_quote
Bonus points:
#+begin_quote
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
#+end_quote
* 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
#+begin_src python
import sys
import json
import pprint
#+end_src
#+RESULTS:
: None
** Pretty Print
#+begin_src python
pp = pp.Printer = pprint.PrettyPrinter(indent=2, compact=True)
#+end_src
And a usage printe
#+begin_src python
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()
#+end_src
** Main
Main function for managing CLI interactions, and dispatch
#+begin_src python
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)
#+end_src
#+begin_src python
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))
#+end_src
** 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.
#+begin_src python
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))
#+end_src
#+RESULTS:
: 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]~.
#+begin_src python
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]
#+end_src
#+RESULTS:
** 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.
#+begin_src python
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
#+end_src
And then we want to generate a table of all the probabilities up to =k=.
#+begin_src python
def multi_turn_table(k):
result = {}
for i in range(6, int(k+1)):
result[i] = multi_turn_single(i)
return result
#+end_src
** Print
#+begin_src python
def print(stuff):
pp.pprint(stuff)
#+end_src
** Script
To run as a script.
#+begin_src python
if __name__ == "__main__":
main()
#+end_src

81
pydiceprob.py Normal file
View file

@ -0,0 +1,81 @@
import sys
import json
import pprint
pp = pp.Printer = pprint.PrettyPrinter(indent=2, compact=True)
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()
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))
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))
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]
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
def multi_turn_table(k):
result = {}
for i in range(6, int(k+1)):
result[i] = multi_turn_single(i)
return result
def print(stuff):
pp.pprint(stuff)
if __name__ == "__main__":
main()