A little bit of procrastination on my part, but thought I would share.
I’m not intending to write a full functional library, we have lodash and ramda for that, but I am writing a few utility functions.
Part of the inspiration to do this came from playing with Map.groupBy. A useful function, that returns a Map type of grouped values.
If you want to then filter, flatMap, reduce that Map type, it first needs to be converted to an Array. The OCD in me, but I don’t like the spread syntax much and would much rather just pass in that map.
So I have just written these reduce functions
// import { TYPES, toString } from type.js
const TYPES = {
'arguments': '[object Arguments]',
'array': '[object Array]',
...
}
/**
* reduceArray - A function that reduces an array to a single value.
* @param {Array} arr - The array to reduce.
* @param {Function} reducer - The reducer function.
* @param {any} accumulator - The initial value for the accumulator.
* @returns {any} The final accumulator value.
* @example
* const sum = (a, b) => a + b;
* reduceArray([1, 2, 3, 4], sum); // returns 10
*/
function reduceArray(arr, reducer, accumulator) {
const length = arr.length;
let i = 0;
if (accumulator === undefined) {
if (!length)
throw TypeError('Reduce of empty array with no initial value');
accumulator = arr[i++];
}
while (i < length)
accumulator = reducer(accumulator, arr[i++]);
return accumulator;
}
/**
* reduce - A function that reduces an iterable collection to a single value.
* @param {Iterable} collection - The collection to reduce.
* @param {Function} reducer - The reducer function.
* @param {any} accumulator - The initial value for the accumulator.
* @returns {any} The final accumulator value.
* @example
* const sumValues = (a, [key, val]) => a + val;
* const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
* reduce(map, sumValues, 0); // returns 6
*/
function reduce(collection, reducer, accumulator) {
if (toString.call(collection) === TYPES.array)
return reduceArray(collection, reducer, accumulator);
let iter = collection[Symbol.iterator]();
if (accumulator === undefined) {
const {done, value} = iter.next();
if (done)
throw TypeError('Reduce of empty iterable with no initial value');
accumulator = value;
}
for (const item of iter)
accumulator = reducer(accumulator, item);
return accumulator;
}
The reduce function will take any iterable, and perform a reduce on it without converting the collection to an Array first. If it is an array it uses the faster arrayReduce, it isn’t an array it calls the symbol iterator on that collection to get an iterator. Something I believe for .. of
does under the hood.
The reason I do this for reduce is that I may want to grab the first item if the accumulator argument is undefined.
for and While loops are still king in JS it seems, being much performant than the built in higher order functions e.g. map, filter etc.
I created my own performance test module to see how the reduce functions in vanilla JS, lodash and my custom library performed.
Setup
A large collection of random numbers
const randomArray = Array.from({length: 1000000}, () => Math.floor(Math.random() * 100));
const randomMap = new Map(randomArray.map((val, i) => [i, val]));
const sum = (a, b) => a + b;
const sumValues = (a, [key, val]) => a + val;
Tests
const reduceMaps = (
timeTests('reduce performance tests with a Map collection')
.test(
'Vanilla JS reduce',
() => {
Array.from(randomMap).reduce(sumValues, 0);
}
)
.test(
'_.loDash reduce',
() => {
_.reduce(_.toArray(randomMap), sumValues, 0);
}
)
.test(
'Custom reduce',
() => {
reduce(randomMap, sumValues, 0);
}
)
);
const reduceArrays = (
timeTests('reduce performance tests with an Array collection')
.test(
'Vanilla JS reduce',
() => {
randomArray.reduce(sum);
}
)
.test(
'_.loDash reduce',
() => {
_.reduce(randomArray, sum);
}
)
.test(
'Custom reduce',
() => {
reduce(randomArray, sum);
}
)
);
reduceMaps.run(1, 25); // 1 iteration, 25 samples
reduceArrays.run(1, 25); // 1 iteration, 25 samples
My test module isn’t the best, but these were my results
Reducing a Map
Reducing an Array
I also tested the Reduce Map collection with measurethat.net
So that is it, as I say just thought I would share
As an aside, I haven’t used Typescript yet, but I am toe dipping with eslint’s jsdoc. Got to say I like it, being able to hover over the function and see it’s details is pretty useful.