Sign in to run and submit your work
Reading is open to everyone. Running code and saving drafts need an account so your work is yours and comes back on your next visit.
or
CODE WORKSPACE
The latency report is computed twice: in SQL for the dashboard and in Python for the weekly summary. They disagree, sometimes by a lot on small groups, and the argument about which one is right has been going on for a month. The SQL uses PERCENTILE_DISC, which returns an actual observed value at the nearest rank.
Write percentiles(values_by_group, wanted) to match it.
Function to write
percentiles(values_by_group: dict, wanted: list) -> list[dict]One row per group with the count, the skipped count, and one column per requested percentile.
How to approach it
Rank from one with a ceiling, then index from zero. The two ends need their own answers.
Sample cases
+ 2 held back until you submit
four endpoints
A group with an outlier tail, a tiny group, one with unusable values, and one with nothing at all.
Input
Argument 1
{
'checkout': [
120,
95,
300,
85,
4000,
110,
130,
90,
105,
115
],
'search': [
10,
12,
11,
13
],
'profile': [
50,
'timeout',
None,
60
],
'empty': []
}Argument 2
[
50,
95,
99
]Returns
| group | count | skipped | p50 | p95 | p99 |
|---|---|---|---|---|---|
| checkout | 10 | 0 | 110 | 4000 | 4000 |
| empty | 0 | 0 | None | None | None |
| profile | 2 | 2 | 50 | 60 | 60 |
| search | 4 | 0 | 11 | 13 | 13 |
no groups
Nothing measured produces an empty report rather than a row of nulls.
Input
Argument 1
{}Argument 2
[
50,
95
]Returns
[] (empty list)
the extremes
The zeroth and hundredth percentiles are the minimum and the maximum, not an index error.
Input
Argument 1
{
'checkout': [
120,
95,
300,
85,
4000,
110,
130,
90,
105,
115
],
'search': [
10,
12,
11,
13
],
'profile': [
50,
'timeout',
None,
60
],
'empty': []
}Argument 2
[
0,
100
]Returns
| group | count | skipped | p0 | p100 |
|---|---|---|---|---|
| checkout | 10 | 0 | 85 | 4000 |
| empty | 0 | 0 | None | None |
| profile | 2 | 2 | 50 | 60 |
| search | 4 | 0 | 10 | 13 |
Constraints
ceil(p / 100 * n) in the sorted values, counting from 1. Every result is a value that was actually measured — never an interpolation between two.None for every percentile and a count of zero, rather than being left out.skipped.count of usable values alongside the percentiles — a p99 over four values is a number, but not a useful one.p followed by the requested value.Worked example
The search group has four values: 10, 11, 12, 13. Its p95 is at rank ceil(0.95 * 4) = 4, which is 13 — the maximum. That is correct and it is also the reason the count belongs in the report: with four samples, p95 and p100 are the same number, and treating it as a tail latency is meaningless.
The naive index — values[int(0.95 * 4)] — is values[3], which happens to agree here. It disagrees at p50: int(0.5 * 4) is 2, giving 12, where the nearest rank is ceil(2.0) = 2, giving 11. And at p0 the two definitions differ by a whole position, while at p100 the naive version indexes past the end of the list and raises. Two definitions that agree on most inputs and differ on small groups is precisely the shape of an argument that lasts a month.
What this tests
That a percentile is a definition, not a formula, and that two reasonable definitions give different answers on the same data. Matching the warehouse exactly is worth more than being subtly more principled than it.
percentiles(values_by_group: dict, wanted: list) -> list[dict]Submit for review to find out what your query gets right, what it gets wrong, and how it compares with the best working query for this exercise.
This scenario runs a full workspace — editor, canvas and results side by side. It needs a laptop or desktop to be usable. Open this page on a bigger screen to start building.