Solving tips
- Do not estimate pass@k by sampling k of the n attempts and checking if any passed — that is high-variance and biased for small n. There is a closed form: the chance all k draws miss is comb(n-c, k) / comb(n, k), so pass@k is one minus that.
- The failure combinatorics only make sense when there are at least k failures to choose from. When n - c < k every k-subset must contain a passing sample, so pass@k for that task is exactly 1.0 — special-case it before calling comb.
- pass@k is a per-task quantity; the reported number is the average across tasks. Keep those two steps separate so an empty suite or a single bad task is easy to reason about.
When you sample a code-generating agent many times per problem, the honest quality metric is not “did the single best sample pass” but “if a user had drawn k samples, how likely is at least one to pass.” That is pass@k. Estimating it by literally drawing k of your n attempts is noisy and biased downward, so the standard defines an unbiased closed-form estimate from the counts alone. This exercise implements that estimator.
The estimator
For a single task you sampled n attempts and c of them passed. Draw k of the n attempts uniformly at random. The draw fails only when all k come from the n - c failing attempts, which happens with probability comb(n - c, k) / comb(n, k). So the per-task pass@k is:
1 - comb(n - c, k) / comb(n, k)
This is the unbiased estimator from the HumanEval / Codex evaluation. The reported pass@k for a benchmark is the mean of this quantity over all tasks.
The one edge to watch: if n - c < k there are not even k failing samples to choose, comb(n - c, k) is 0, and the task’s pass@k is 1.0. math.comb already returns 0 when the second argument exceeds the first, but handling it explicitly keeps the intent obvious and avoids relying on that behavior.
Task
Complete pass_at_k(results, k):
- If
resultsis empty, return0.0. - For each
(n, c)task, compute the per-task estimate. Ifn - c < k, the estimate is1.0; otherwise it is1 - comb(n - c, k) / comb(n, k). - Return the average of the per-task estimates across all tasks.
Use math.comb. Do not approximate by sampling.
Example
results = [
(5, 3), # 5 attempts, 3 passed
(5, 0), # 5 attempts, none passed
(10, 10), # all 10 passed
(4, 1), # 4 attempts, 1 passed
]
# per task at k=2:
# (5,3): 1 - comb(2,2)/comb(5,2) = 1 - 1/10 = 0.9
# (5,0): 1 - comb(5,2)/comb(5,2) = 1 - 10/10 = 0.0
# (10,10): n-c = 0 < 2 -> 1.0
# (4,1): 1 - comb(3,2)/comb(4,2) = 1 - 3/6 = 0.5
round(pass_at_k(results, k=2), 4) # -> 0.6
round(pass_at_k(results, k=1), 4) # -> 0.4625 (mean of 0.6, 0.0, 1.0, 0.25)
Constraints
n >= 1and0 <= c <= nfor every task;1 <= k <= n.- Use integer combinatorics (
math.comb), not floating-point factorials, to avoid overflow and precision loss for largen. - Treat each task independently; the result is a plain average (every task weighted equally).
- Return
0.0for an emptyresultslist.