Implement countBy(collection, iteratee) — walk an array and tally how many elements fall into each bucket, where the bucket for an element is decided by iteratee. Think of sorting a deck of cards into piles by suit and then reporting "13 hearts, 13 spades, …", except the rule for which pile a card belongs to is something you pass in. This mirrors Lodash's _.countBy: the iteratee can be a function you call on each element, or a property-name string as shorthand for "group by this field."
// collection: T[] — the array to tally.
// iteratee: ((el: T) => K) — a function returning the bucket key, OR
// | string — a property name; shorthand for el => el[name].
// returns: Record<string, number>
// An object mapping each produced key (coerced to a string) to the
// COUNT of elements that produced it.
function countBy(collection, iteratee): Record<string, number>;
// Function iteratee: bucket floats by their integer part.
countBy([6.1, 4.2, 6.3], Math.floor);
// → { '6': 2, '4': 1 }
// Function iteratee: split into even and odd piles.
countBy([1, 2, 3, 4, 5], (n) => (n % 2 === 0 ? 'even' : 'odd'));
// → { odd: 3, even: 2 }
// Property-name iteratee: group people by their city field.
const people = [
{ name: 'Ada', city: 'London' },
{ name: 'Linus', city: 'Helsinki' },
{ name: 'Grace', city: 'London' },
];
countBy(people, 'city');
// → { London: 2, Helsinki: 1 }
countBy(items, 'city') behaves like countBy(items, (x) => x.city).6 and a key of '6' land in the same bucket; the returned object's keys are always strings.groupBy).countBy([], fn) returns {}.{} already responds to result['toString'] and result['constructor'] — make sure a bucket named "toString" counts like any other key rather than colliding with an inherited method.