foldBy
Folds the values under each key in one pass — the aggregate without the groups.
Lecture
foldBy is fold run once
per key instead of once over the whole source. Each element picks its key,
and its value is folded into that key's accumulator — so the result is a
Map<K, Acc> of answers, not of elements.
The reason it exists is what it doesn't do.
groupBy followed by a fold per
group has to build a List for every key first: allocation
proportional to the input, for an answer proportional to
the number of keys. When all you want is the total per
category, those lists are built and thrown away. foldBy
accumulates straight into the result map, like the hand-written loop:
// what you would write by hand
for (final t in txns) {
totals[t.category] = (totals[t.category] ?? 0) + t.amount;
}
// the same thing, named
foldBy((Tx t) => t.category, 0.0, (sum, t) => sum + t.amount, txns);
On a million transactions across five categories, grouping first costs
2.7× the hand-written loop. foldBy costs
0.91× of it — it is slightly faster than the loop
beside it, and that is not a rounding artefact. The loop reads the map and
then writes it back, so every transaction hashes its category twice;
foldBy folds into a mutable cell held in the map, so the map is
written once per category rather than once per transaction. Several
of the Dart vs FxDart examples
moved onto it for exactly this reason.
Don't over-read the margin: the fold callback here is one addition, so the
map is most of the work. Give it a heavier accumulator and the saving is
still there but disappears into the callback's own cost — see the note at
the bottom about records. The reason to reach for foldBy is
that it says what you mean; being a shade faster than the loop is a bonus,
not the argument.
Keys come out in first-seen order, like
groupBy. Not an FxTS port — the shape is Kotlin's
groupingBy().fold().
fold, seed is one value used as the starting
point for every key. That is fine for numbers and strings, which
you fold into new values. A mutable seed — a list, a set,
a map — would be shared by every key and mutated by all of them. If you
need to accumulate into a mutable structure per group, use
groupBy.
Demo 1 · Basics
Demo 2 · Async
Try it yourself
Exercise: the demo counts words per first letter. Change
it to total the letters under each first letter, so
fig and fx give {f: 5}.
groupBy there.