Implement a function countBy(array, iteratee)
that creates an object composed of keys generated from the results of running each element of array
thru iteratee
. The corresponding value of each key is the number of times the key was returned by iteratee
. iteratee
s can either be:
iteratee
functions is invoked with one argument: (value).'length'
can be used to return the number of elements of arrays.countBy(array, iteratee);
array
(Array): The array to iterate over.iteratee
(Function): The iteratee function to transform elements. The function is invoked with one argument: (value).(Object): Returns the composed aggregate object.
countBy([6.1, 4.2, 6.3], Math.floor);// => { '4': 1, '6': 2 }countBy(['one', 'two', 'three'], 'length');// => { '3': 2, '5': 1 }
results
object to store the count of occurrences of each key.iteratee
function. If iteratee
is already a function, we can use it as-is. Otherwise, if a string value was provided, a function is created to access that property on an element.iteratee(element)
. If the key does not exist within the results
object, set the value for that key to 0. Next we can increment the value for that key./*** @param {Array} array The array to iterate over.* @param {Function|string} iteratee The function invoked per iteration.* @returns {Object} Returns the composed aggregate object.*/export default function countBy(array, iteratee) {const result = {};const iterateeFunc =typeof iteratee === 'function' ? iteratee : (value) => value[iteratee];for (const element of array) {const key = iterateeFunc(element);if (!Object.prototype.hasOwnProperty.call(result, key)) {result[key] = 0;}result[key]++;}return result;}
An alternative way to increment the result
counter is to use the nullish coalescing assignment operator to set the value to 0 if key
doesn't exist within result
. Note that using nullish coalescing assignment operator means you might be accessing inherited properties, which is not desired, but since the object is created via Object.create(null)
, there will not be inherited properties and is safe to use.
/*** @param {Array} array The array to iterate over.* @param {Function|string} iteratee The function invoked per iteration.* @returns {Object} Returns the composed aggregate object.*/export default function countBy(array, iteratee) {const result = Object.create(null);for (const element of array) {const key =typeof iteratee === 'function' ? iteratee(element) : element[iteratee];result[key] ??= 0;result[key]++;}return result;}
import countBy from './count-by';describe('countBy', () => {test('empty array', () => {expect(countBy([], Math.floor)).toEqual({});});describe('function iteratees', () => {test('single-element arrays', () => {expect(countBy([6.1], Math.floor)).toEqual({ 6: 1 });});test('two-element arrays', () => {expect(countBy([6.1, 4.2], Math.floor)).toEqual({ 4: 1, 6: 1 });});test('multiple element arrays', () => {expect(countBy([6.1, 4.2, 6.3], Math.floor)).toEqual({ 4: 1, 6: 2 });});test('keys that are also properties', () => {expect(countBy(['one', 'two', 'three'], (val: string) => 'length'),).toEqual({length: 3,});});});describe('property iteratees', () => {test('single-element arrays', () => {expect(countBy(['one'], 'length')).toEqual({ 3: 1 });});test('two-element arrays', () => {expect(countBy(['one', 'two'], 'length')).toEqual({ 3: 2 });});test('multiple element arrays', () => {expect(countBy(['one', 'two', 'three'], 'length')).toEqual({3: 2,5: 1,});});});test('does not mutate the original array', () => {const arr = [6.1, 4.2, 6.3];const copy = arr.slice();const result = countBy(arr, Math.floor);expect(result).toEqual({ 4: 1, 6: 2 });expect(arr).toEqual(copy); // Ensure original array is unchanged});});
console.log()
statements will appear here.