Back to WCA Statistics

Rolling Average

World record

Lay out a cuber's entire official attempt history in chronological order, slide a window of size N over it computing the WCA trimmed mean — N ∈ {3, 5, 12, 25, 50, 100, 1000}. Each window emits a number; the smallest across their career = their AoN PB. Unlike the WCA official "trimmed mean of 5 within one round," this N slides across comps and rounds.

It probes long-horizon consistency — an Ao1000 PB means 1000 consecutive official attempts at top form. UI dropdown switches N; each N gets its own Ranking + WR History. The aggregator stitches 7 AverageOfX children into one stat.

By the numbers

7
N tiers
3 / 5 / 12 / 25 / 50 / 100 / 1000
top 15~2000
Candidate top-N
Varies by event (333: 15; 333bf: 2000)
5%
Trim ratio
ceil(N × 5%) per side, ~10% total
SQL queries
7 children share one cached row set

Data source

Base SQL pulls all attempts of candidate cuber — candidates = (per-event top-N by average PB) UNION (single / average WR holders ever). Top-N varies: 333: 15, 333bf: 2000, most events 30, FMC / pyram / clock / etc. 300.

The 7 children (Ao3 ... Ao1000) share one cached row set — first child runs the SQL, others reuse, dodging 6× ~60s redundant queries. For FMC, move counts are ×100 to map onto the centisecond domain.

sql
SELECT person_id, country_id, event_id,
       GROUP_CONCAT(ra.value ORDER BY ra.attempt_number) AS attempts,
       competition_link, competition.start_date
FROM results result
JOIN persons person ON person.wca_id = person_id AND person.sub_id = 1
JOIN competitions competition ON competition.id = competition_id
JOIN round_types round_type ON round_type.id = round_type_id
JOIN ( /* candidate = top-N UNION WR holders */ ) candidates USING (event_id, person_id)
WHERE event_id NOT IN ('333mbf', '333mbo')
ORDER BY competition.start_date, round_type.rank;

Algorithm / pipeline

1
One attempts query → shared cache
getSharedQueryRows() lazy-loads one giant query; the 7 children reuse it — no 7× re-queries. Cache survives across children, gets clearSharedCache()'d after the aggregator finishes.
2
Group by (event, person), flatten attempts
Within each event, each cuber's rounds sort ascending by (start_date, round_type.rank); each round's attempts split and stream into solves[] — DNF → Infinity (gets trimmed off if possible), 0 skipped (not-attempted).
3
Sliding window + WCA trimmed mean
When solves.length == N, compute the trimmed mean: sort, drop ceil(N × 5%) from each side, average the rest. If a trimmed window still contains Infinity → DNF the whole average. Then shift() the window.
4
Per person: best + pbHistory
Each cuber holds a best AoN + a pbHistory list. To avoid memory blowup (Ao1000 × many PBs), pbHistory stores formatted csv strings instead of solve arrays.
5
Emit ranking + WR history
Top 10 per event by best AoN ascending. WR History merges everyone's pbHistory, sorts by (endDate, value), keeps strict improvements only, reverses for newest-first.

Key formulae

WCA trimmed mean (window = N)
AoN = (1 / (N - 2k)) · Σ sᵢ for i ∈ (k, N − k], k = ⌈N × 0.05⌉
s₁ ≤ s₂ ≤ ... ≤ s_N is the sorted window; drop k = ⌈N × 5%⌉ per side, arithmetic mean the rest. N=5 → k=1 (the WCA standard ao5); N=1000 → k=50.

Caveats & edges

Related stats & links