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 chart is unreadable minute by minute, so the team wants a trailing average over the last few minutes per service. The metrics table is written by several collectors, so the rows arrive neither grouped by service nor ordered by minute, and a collector that was down contributes a row with no measurement.
Write rolling_average(rows, key_field, order_field, value_field, window).
Function to write
rolling_average(rows: list[dict], key_field: str, order_field: str, value_field: str, window: int) -> list[dict]Copies of the rows with a rolling_avg field, ordered by key then by the ordering field.
How to approach it
One bounded window per key, held in a dict; a deque with a maxlen does the eviction for you.
Sample cases
+ 2 held back until you submit
two services interleaved
Rows arrive ungrouped and out of order, and one minute has no measurement at all.
Input
Argument 1
| service | minute | latency |
|---|---|---|
| api | 09:03 | 120 |
| db | 09:01 | 8 |
| api | 09:01 | 100 |
| api | 09:02 | 140 |
| db | 09:02 | 12 |
| api | 09:04 | None |
| api | 09:05 | 200 |
Argument 2
'service'Argument 3
'minute'Argument 4
'latency'Argument 5
3Returns
| service | minute | latency | rolling_avg |
|---|---|---|---|
| api | 09:01 | 100 | 100 |
| api | 09:02 | 140 | 120 |
| api | 09:03 | 120 | 120 |
| api | 09:04 | None | 120 |
| api | 09:05 | 200 | 153.3333 |
| db | 09:01 | 8 | 8 |
| db | 09:02 | 12 | 10 |
no rows
Nothing to average returns an empty list rather than a row of nulls.
Input
Argument 1
[] (empty list)
Argument 2
'service'Argument 3
'minute'Argument 4
'latency'Argument 5
3Returns
[] (empty list)
a window of one
A window of one makes the average the value itself, except where there is no value.
Input
Argument 1
| service | minute | latency |
|---|---|---|
| api | 09:03 | 120 |
| db | 09:01 | 8 |
| api | 09:01 | 100 |
| api | 09:02 | 140 |
| db | 09:02 | 12 |
| api | 09:04 | None |
| api | 09:05 | 200 |
Argument 2
'service'Argument 3
'minute'Argument 4
'latency'Argument 5
1Returns
| service | minute | latency | rolling_avg |
|---|---|---|---|
| api | 09:01 | 100 | 100 |
| api | 09:02 | 140 | 140 |
| api | 09:03 | 120 | 120 |
| api | 09:04 | None | 120 |
| api | 09:05 | 200 | 200 |
| db | 09:01 | 8 | 8 |
| db | 09:02 | 12 | 12 |
Constraints
window - 1 usable values before it, within the same key. It never reaches into another key.order_field, ascending, compared as text.None rather than zero.rolling_avg added.Worked example
The api service reports 100, 140 and 120 in its first three minutes, so the third row's three-minute average is 120.0. The db rows sit between them in the file; a single window shared across services would have averaged 8 and 12 into the api figures and produced a latency chart with no relationship to anything.
At 09:04 the api collector reports no latency. The window does not advance — there is nothing to add — so the row carries the average of 100, 140 and 120 that preceded it. Treating the missing value as zero would drop the average to 90 and put a dip in the chart at the exact moment the collector was broken, which is the opposite of what the reader needs to see.
What this tests
Per-group windowed state, and the decision about what a missing value does to a window. Both mistakes — one window for everything, and missing-means-zero — produce a chart that looks smooth and says something false.
rolling_average(rows: list[dict], key_field: str, order_field: str, value_field: str, window: int) -> 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.