Solve problem 323
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
from random import randint
|
||||||
|
from math import factorial
|
||||||
|
from collections import defaultdict
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
|
||||||
|
def binomial(n: int, k: int) -> Fraction:
|
||||||
|
return Fraction(factorial(n), (factorial(k) * factorial(n - k)))
|
||||||
|
|
||||||
|
|
||||||
|
def f(k: int, n: int) -> Fraction:
|
||||||
|
# Probability that k out of n trials succeed with rate p per trial.
|
||||||
|
p = Fraction(1, 2)
|
||||||
|
return binomial(n, k) * p**k * (1 - p) ** (n - k)
|
||||||
|
|
||||||
|
|
||||||
|
def count(exp_n: Fraction, step: int, dist) -> Fraction:
|
||||||
|
step += 1
|
||||||
|
|
||||||
|
# For each number `n` of remaining "ones" calculate the probability that
|
||||||
|
# 0..`n` ones are remanining for that probability. We can then calculate a
|
||||||
|
# new probability mass by multiplying the current with the new probability
|
||||||
|
# for a given `n`.
|
||||||
|
ndist = defaultdict(Fraction)
|
||||||
|
for n, p in dist:
|
||||||
|
for i in range(n + 1):
|
||||||
|
np = f(i, n)
|
||||||
|
ndist[n - i] += p * np
|
||||||
|
|
||||||
|
# Probability that we reach `0` at this step counts to total expected
|
||||||
|
# number of steps.
|
||||||
|
exp_n += ndist[0] * step
|
||||||
|
del ndist[0]
|
||||||
|
|
||||||
|
# Drop probabilities small enough that they seem englible for the precision
|
||||||
|
# we need which is 10 digits after the decimal point.
|
||||||
|
dist = []
|
||||||
|
for n, p in ndist.items():
|
||||||
|
if p > Fraction(1, 10**15):
|
||||||
|
dist.append((n, p))
|
||||||
|
if dist:
|
||||||
|
return count(exp_n, step, dist)
|
||||||
|
return exp_n
|
||||||
|
|
||||||
|
|
||||||
|
def stackoverflow():
|
||||||
|
# I found this expression when googling (geminiing) an expression for this
|
||||||
|
# type of problem. However, it did not yield the correct solution because I
|
||||||
|
# did not use Fractions. I then came up with `count` and was somehow clever
|
||||||
|
# enough to realize that I would have to use Fractions and after I came
|
||||||
|
# back I updated stackoverflow accordingly just to see if I had implemented
|
||||||
|
# the expression correctly.
|
||||||
|
|
||||||
|
# https://mathoverflow.net/questions/41604/what-is-the-expected-maximum-out-of-a-sample-size-n-from-a-geometric-distribut
|
||||||
|
t = Fraction()
|
||||||
|
p = Fraction(1, 2)
|
||||||
|
q = 1 - p
|
||||||
|
n = 32
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
c = binomial(n, i)
|
||||||
|
c *= (-1) ** (i + 1)
|
||||||
|
c *= Fraction(1, 1 - q**i)
|
||||||
|
t += c
|
||||||
|
return float(t)
|
||||||
|
|
||||||
|
|
||||||
|
def euler_323():
|
||||||
|
n_stackoverflow = stackoverflow()
|
||||||
|
n = count(Fraction(), 0, [(32, Fraction(1, 1))])
|
||||||
|
assert round(float(n_stackoverflow), 10) == round(float(n), 10)
|
||||||
|
return round(float(n), 10)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
solution = euler_323()
|
||||||
|
print(f"e323.py: {solution}")
|
||||||
|
assert solution == 6.3551758451
|
||||||
Reference in New Issue
Block a user