First commit.
This commit is contained in:
parent
69535e749e
commit
ee032555eb
5 changed files with 279 additions and 5 deletions
4
LICENSE
4
LICENSE
|
@ -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.
|
||||||
|
|
||||||
|
|
BIN
Python_Programming_Interview_Problem_1.pdf
Normal file
BIN
Python_Programming_Interview_Problem_1.pdf
Normal file
Binary file not shown.
|
@ -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
196
README.org
Normal 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
81
pydiceprob.py
Normal 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()
|
Loading…
Reference in a new issue