このページはまだ翻訳されていないため、英語で表示されます。 翻訳に参加する

foldBy

Folds the values under each key in one pass — the aggregate without the groups.

Map<K, Acc> foldBy<A, K, Acc>(K Function(A a) key, Acc seed, Acc Function(Acc acc, A a) f, Iterable<A> iterable) Future<Map<K, Acc>> foldByAsync<A, K, Acc>(FutureOr<K> Function(A a) key, FutureOr<Acc> seed, FutureOr<Acc> Function(Acc acc, A a) f, FxAsyncIterable<A> iterable) Map<K, Acc> Fx.foldBy<K, Acc>(K Function(T a) key, Acc seed, Acc Function(Acc acc, T a) f) // chain (sync) Future<Map<K, Acc>> FxAsync.foldBy<K, Acc>(FutureOr<K> Function(T a) key, FutureOr<Acc> seed, FutureOr<Acc> Function(Acc acc, T a) f) // chain (async)

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().

The seed is a value, not a factory. Just as in 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}.

When not to use it: an accumulator that needs two running values — a mean needs a sum and a count — has to carry them in a record, and a record is allocated per element. That costs more than the grouping it avoids; reach for groupBy there.
Related: groupBy — keep the elements instead of folding them · countByfoldBy with a counter, pre-named · fold — the same fold over one accumulator · groupedBy — grouping that stays in the chain