Teaser 3210: Random rabbit
From The Sunday Times, 31st March 2024 [link] [link]
Every year Easter Bunnies must pass a series of demanding tests, the most important being the double jump. Rabbits perform a single jump comprising two hops, with the total distance scoring. For both hops, Max knows he has an equal chance of jumping any distance between two limits. For the first hop, the limits are 80 and 100cm. For his weaker second hop, these limits decrease but keep the same proportion.
However, the instructors have increased the required standard from 152 to 163cm. Max is worried; he can still pass, but his chance is half what it was before.
What, as a percentage, is his chance of passing this year?
This brings the total number of Teaser puzzles posted on the site to 1024 (= 2^10), which is roughly 1/3 of all published Teaser puzzles.
[teaser3210]
Jim Randell 5:47 pm on 29 March 2024 Permalink |
We can consider the possible jumps to be a region of the cartesian plane between x = [80, 100] (= hop 1) and y = [80f, 100f] (= hop 2), where f ∈ (0, 1) gives the reduction for the second hop.
The probabilities are then calculated from the proportion of this rectangular region that is above the lines x + y = 152 (last year) and x + y = 163 (this year). (Assuming that hops are uniformly distributed).
This Python program finds the value of f numerically, such that the second area (dark blue) is half the first area (both blues), and then calculates the corresponding probabilities.
It runs in 71ms. (Internal runtime is 179µs).
Run: [ @replit ]
from enigma import (find_value, fdiv, sq, inf, printf) # find area where x + y > t def A(t, f): # where does x + y = t hit y = 80f and 100f (x1, x2) = (t - 80 * f, t - 100 * f) if x1 < 80: # entire area = 20 * 20f return 400 * f if x2 < 80: # remove bottom left corner return 400 * f - 0.5 * sq(x1 - 80) if x1 < 100: # trapezium return 20 * f * (10 * f + 100 - x1) if x2 < 100: # top left corner return 0.5 * sq(100 - x2) # otherwise, zero area return 0 # find ratio of areas for the two scenarios def fn(f): # area for t = 152 A1 = A(152, f) if A1 == 0: return inf # area for t = 163 A2 = A(163, f) # return the ratio A2/A1 return fdiv(A2, A1) # find when fn(f) = 0.5, for f in the interval (0, 1) r = find_value(fn, 0.5, 0.0, 1.0) f = r.v # calculate probabilities T = 400 * f # total area P1 = fdiv(A(152, f), T) P2 = fdiv(A(163, f), T) # output solution printf("{P1:.1%} -> {P2:.1%} [f={f:.2f}]")Solution: Max’s chance of passing this year is 45%.
The value of f is 0.8, so the range for the second hop is 64 – 80 cm.
This gives a total area of the rectangle as: 20 × 16 = 320.
For last year we want the area of the rectangle above the line (80, 72) – (88, 64) which gives: 288.
For this year we want the area above the line (83, 80) – (99, 64) which gives: 144.
And so the probability this year (144/320 = 45%) is exactly half of the probability last year (288/320 = 90%).
LikeLike
Frits 7:38 pm on 29 March 2024 Permalink |
For the time being an approximation, less than half a second with PyPy.
Starts to slow down quickly when increasing the precision.
from itertools import product lmts = [80, 100] stds = {2023: 152, 2024: 163} fr, to = 52, 79 done = 0 m = 1 # multiplication factor while not done: lmts = [10 * x if m > 1 else x for x in lmts] rng1 = range(lmts[0], lmts[1] + 1) # a = lower limit for 2nd jump for a in range(to, fr - 1, -1): rng2 = [a * x / lmts[0] for x in rng1] # chance of passing per year P2023 = sum(x + y >= m * stds[2023] for x, y in product(rng1, rng2)) / (len(rng1) * len(rng2)) P2024 = sum(x + y >= m * stds[2024] for x, y in product(rng1, rng2)) / (len(rng1) * len(rng2)) # check if we are close to 50% if abs(P2024 / P2023 - 0.500) <= 1e-3: print(f"answer: approximately {round(100 * P2024, 1)}%") done = 1 break if P2024 / P2023 < 0.5: break # try to get closer to 50,00% by increasing the ranges by 10 m *= 10 (fr, to) = (10 * a, 10 * (a + 1))LikeLike
Frits 2:50 pm on 30 March 2024 Permalink |
More efficient.
from math import ceil lmts = [80, 100] stds = {2023: 152, 2024: 163} # if 2nd hop is below 52 we can never reach 152 fr, to = 52, 79 done = 0 # calculate the chance if we pick one element from r1 and r2 that # their sum is greater or equal <std> def calcP(r1, r2, std): miss, strt = 0, 0 ln1, ln2 = len(rng1), len(rng2) df1, df2 = rng1[1] - rng1[0], rng2[1] - rng2[0] # factor f: f * df2 >= df1 --> f >= df1 / df2 f = ceil(df1 / df2) r2rev = rng2[::-1] # if x1 + x2 is lower than <std> for this x2 then x1 + (x2 + df2) >= std # --> (x1 - df1) + (x2 + df2 + f * df2) >= std (as f * df2 >= df1) # meaning: for the next x1 iteration we can select a subset of rng2 # loop from the high value to low value for x1 in rng1[::-1]: for j, x2 in enumerate(r2rev[strt:], start=strt): if x1 + x2 < std: strt = j - f if j - f >= 0 else j - 1 # increment number of combinations below the standard miss += (ln2 - j) # for the next x1 we can start checking x2 as of (x2 + k * df2) break else: continue # a break has occurred if j == 0: # for lower x1's add maximum missing number (ln2) miss += (ln2 * (x1 - rng1[0])) break return ((ln1 * ln2) - miss) / (ln1 * ln2) m = 1 # multiplication factor while not done: lmts = [10 * x if m > 1 else x for x in lmts] rng1 = range(lmts[0], lmts[1] + 1) # a = lower limit for 2nd jump for a in range(to, fr - 1, -1): rng2 = [a * x / lmts[0] for x in rng1] # chance of passing per year P2023 = calcP(rng1, rng2, m * stds[2023]) P2024 = calcP(rng1, rng2, m * stds[2024]) # check if we are close to 50% if abs(P2024 / P2023 - 0.5) <= 1e-4: print(f"answer: approximately {round(100 * P2024, 2)}%") done = 1 break if P2024 / P2023 < 0.5: break # try to get closer to 50,00% by increasing the elements by 10 m *= 10 (fr, to) = (10 * a, 10 * (a + 1))PyPy is a lot faster than CPython. I don’t recall a program where PyPy was more than 17 times quicker.
LikeLike
Frits 12:11 pm on 1 April 2024 Permalink |
line 24 can be done more efficiently.
The program runs now in 200ms/500ms for PyPy/CPython.
LikeLike