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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
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 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