| name | mongez-collection-math |
| description | Aggregate reducers (`sum`, `min`, `max`, `average`, `avg`, `median`), per-element arithmetic (`plus`, `minus`, `multiply`, `divide`, `modulus`, `increment`, `decrement`, `double`, `half`), parity filters (`even`, `odd`, `evenIndexes`, `oddIndexes`), and counting (`count`, `countValue`, `countBy`). Covers the reinforcements quirks (`min`/`max`-of-empty = 0, `average`-of-empty = NaN, divide-by-zero throws) and the keyed-form source-mutation gotcha.
TRIGGER when: code calls `c.sum`, `c.min`, `c.max`, `c.average`, `c.avg`, `c.median`, `c.plus`, `c.minus`, `c.multiply`, `c.divide`, `c.modulus`, `c.increment`, `c.decrement`, `c.double`, `c.half`, `c.even`, `c.odd`, `c.evenIndexes`, `c.oddIndexes`, `c.count`, `c.countValue`, or `c.countBy` on an `ImmutableCollection`; user asks "how do I sum / average / total / max / min on a collection field", "why does min return 0", "how to bump every item by 1", "why does divide throw".
SKIP: math without a fluent chain or operator filter — use `mongez-reinforcements-arrays` (lighter `sum` / `min` / `max` / `average` / `median` / `count` / `countBy`); the higher-level "when to use which math method" tutorial — use `mongez-collection-math-aggregation`; aggregation downstream of `groupBy` — see `mongez-collection-transforming` or `mongez-collection-recipes`.
|
Math, parity, and counting
All aggregates accept an optional dot-notation key for objects-of-records.
Aggregates
c.min(key?): number
c.max(key?): number
c.sum(key?): number
c.average(key?): number
c.avg(key?): number
c.median(key?): number
collect([1, 2, 3, 4, 5]).sum();
collect([{ price: 10 }, { price: 20 }]).sum("price");
collect([{ total: { price: 10 } }]).sum("total.price");
Edge cases
min / max on an empty collection return 0 (preserving the legacy reinforcements convention). On a non-empty collection the wrapper computes the true minimum/maximum directly — collections of all-positive numbers return their actual min, not 0.
average of an empty collection is NaN (0 / 0).
NaN values are skipped silently in min, max, sum, average, median.
Per-element arithmetic
All of these return a NEW collection with the value applied to each element (or to a keyed value).
c.plus(amount)
c.plus(key, amount)
c.minus(amount) / minus(key, amount)
c.multiply(amount) / multiply(key, amount)
c.divide(amount) / divide(key, amount)
c.modulus(amount) / modulus(key, amount)
c.increment(key?)
c.decrement(key?)
c.double(key?)
c.half(key?)
collect([10, 20]).plus(5);
collect([{ age: 20 }, { age: 30 }]).plus("age", 1);
Keyed forms — safe by default for plain objects
The keyed forms (plus("key", amount), minus("key", ...), etc.) shallow-clone each plain-object item via an internal cloneForSet helper before calling set(clone, key, value). The source objects are not mutated:
const src = [{ age: 20 }];
collect(src).plus("age", 1);
src;
Class instances and nested object references are still passed through by reference (the clone is shallow). If you need deep immutability for nested structures, clone the input deeply before wrapping.
Division / modulus by zero
divide / modulus throw a plain Error when the divisor is zero. The error message is "Cannot divide by zero" / "Cannot have a modulus of zero".
Parity
c.even(key?)
c.odd(key?)
c.evenIndexes()
c.oddIndexes()
collect([1, 2, 3, 4, 5]).even();
collect([1, 2, 3, 4, 5]).evenIndexes();
Counting
c.count(keyOrCallback): number
c.countValue(value): number
c.countBy(key): Record<string, number>
collect(users).count("email");
collect(users).count(u => u.active);
collect([1, 2, 1, 1, 3]).countValue(1);
collect(users).countBy("role");