Smoothed zone changes
Requirement
A temperature sensor reports twelve raw readings per day. Smooth them
with a 3-reading moving average, classify each smoothed
value into a zone (cool < 20° ≤ ok < 25°
≤ hot), and report every zone transition —
which zone it left, which it entered, and the smoothed values on both
sides. A day with no transitions prints a single
stable line instead of nothing. The data for two July days is
in the code; both versions must print the lines shown under
Expected output.
Expected output
2026-07-14 (3-reading moving average): cool → ok (avg 18.6 → 21.2) ok → hot (avg 21.2 → 25.6) hot → ok (avg 25.6 → 24.9) 2026-07-15 (3-reading moving average): stable — no zone changes
Side by side
Native Dart
FxDart
Why they differ
Every stage of this task needs to see neighboring elements,
and that is exactly where native Dart runs out of vocabulary: no
sliding window, no adjacent-dedup, no successor pairing — in the
standard library or in package:collection
(slices tiles without overlap). So the native version is
three index loops, each carrying its own mutable state: a windowed sum,
a runStarts list compared against its own tail, and an
i - 1 lookback for the transition lines, plus a
final isEmpty patch-up for the stable day.
The FxDart chain states the five stages in the order the data flows:
windowed(3) → average per window,
uniqAdjacentBy(zone) keeps the first smoothed value of
each zone run, pairwise turns run-starts into
(from, to) transitions, and ifEmpty supplies the
stable-day line inside the pipeline instead of an if-check after it.
Each fragment is independently testable, and none of it re-implements
window bounds. These four operators are pull-model ports of the Rx
windowing family.
Benchmark
N = 100
Time Tie
Peak memory Tie
N = 10,000
Time Tie
Peak memory Tie
N = 1,000,000
Time FxDart wins
Peak memory FxDart wins
Bars are medians of repeated timed iterations in fresh processes per side (small N is batched for timer resolution). Sides within 5% of each other — or within 0.6 ms, a difference no person can perceive — count as a tie; close relative races are re-measured up to 5 runs. In an app, anything under a few milliseconds is invisible to the user regardless of which bar is shorter. Memory is peak process RSS. The Dart VM and the dataset are identical on both sides, so the difference between the two bars is what the pipeline itself holds onto.