Skip to content

Instantly share code, notes, and snippets.

@quidmonkey
Last active March 16, 2018 00:43
Show Gist options
  • Save quidmonkey/1e37716db7d4e1a6af5181d8fb0f4a6c to your computer and use it in GitHub Desktop.
Save quidmonkey/1e37716db7d4e1a6af5181d8fb0f4a6c to your computer and use it in GitHub Desktop.
Various JavaScript Implementations of Reduce
// utils
// unit test - assert 2 values are equal
const assert = (assertion, actual, expected) => {
console.log('~~~', assertion, ':', actual === expected);
};
// clone a value
// objects are only shallow cloned
const clone = (val) => {
if (Array.isArray(val)) {
return [...val];
} else if (typeof val === 'object' && val != null) {
return {...val};
}
return val;
};
// functional compose
const compose = (...fs) => fs.reduce((f, g) => (...args) => f(g(...args)));
// generator runner
const spawn = (gen) => {
return (...args) => {
const it = gen.apply(this, args);
return Promise.resolve()
.then(function step(val) {
const res = it.next(val);
if (res.done) {
return res.value;
}
return Promise.resolve(res.value)
.then(step)
.catch(it.throw.bind(it));
});
};
};
// reducers
// simple synchronous reducer
const reduce = (reducer, initialValue, list) => {
let accumulator = initialValue;
for (let i = 0; i < list.length; i++) {
accumulator = reducer(
accumulator,
clone(list[i]),
i,
clone(list)
);
}
return accumulator;
};
// synchronous reducer using composition
const reduceFunctional = (reducer, initialValue, list) => {
const reducers = Array(list.length).fill(
([accumulator, currentIndex]) => {
const reducedValue = reducer(
accumulator,
clone(list[currentIndex]),
currentIndex,
clone(list)
);
return [reducedValue, currentIndex + 1];
}
);
return compose(([accumulator]) => accumulator, ...reducers)([initialValue, 0]);
};
// async/await reducer
const reduceAsync = async (reducer, initialValue, list) => {
let accumulator = initialValue;
let currentIndex = -1;
for (const currentValue of list) {
accumulator = await reducer(
accumulator,
clone(currentValue),
currentIndex++,
clone(list)
);
}
return accumulator;
};
// generator reducer
const reduceGenerator = spawn(function* (reducer, initialValue, list) {
let accumulator = initialValue;
let currentIndex = -1;
for (const currentValue of list) {
accumulator = yield reducer(
accumulator,
clone(currentValue),
currentIndex++,
clone(list)
);
}
return accumulator;
});
// promise reducer
// thanks to https://github.com/tomdottom for this implementation
const reducePromise = (reducer, initialValue, list) => {
let promise = Promise.resolve(initialValue);
for (const currentIndex in list) {
promise = promise.then((accumulator) => {
return reducer(
accumulator,
clone(list[currentIndex]),
currentIndex,
clone(list)
);
});
}
return promise;
};
// promise reducer using sync reduce as a proxy
const reducePromiseProxy = (reducer, initialValue, list) => {
return reduce(
(...args) => {
return Promise.resolve(args[0])
.then((accumulator) => {
return reducer(accumulator, ...args.slice(1));
});
},
initialValue,
list
);
};
// promise reducer using recursion
const reducePromiseRecurse = (reducer, initialValue, list) => {
const iterate = (accumulator, currentIndex) => {
return Promise.resolve()
.then(() => {
if (currentIndex < list.length) {
return reducer(
accumulator,
clone(list[currentIndex]),
currentIndex,
clone(list)
)
.then((newAccumulator) => {
return iterate(newAccumulator, currentIndex + 1);
});
}
return accumulator;
});
};
return iterate(initialValue, 0);
};
// unit tests
const syncTest = (assertion, unit) => {
const numbers = [...Array(5).keys()];
const reducer = (accumulator, currentValue) => {
return accumulator + currentValue;
};
const expected = 10;
const actual = unit(reducer, 0, numbers);
assert(assertion, actual, expected);
};
// uses async/await & promises to simplify the test
const asyncTest = async (assertion, unit) => {
const numbers = [...Array(5).keys()];
const reducer = async (accumulator, currentValue) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(accumulator + currentValue);
}, 200);
});
};
const expected = 10;
const actual = await unit(reducer, 0, numbers);
assert(assertion, actual, expected);
};
// magic!
const main = async () => {
syncTest('reduce: should sum a list of numbers synchonously', reduce);
syncTest('reduceFunctional: should sum a list of numbers synchronously using composition', reduceFunctional);
await asyncTest('reduceAsync: should sum a list of numbers using async/await', reduceAsync);
await asyncTest('reduceGenerator: should sum a list of numbers using generators', reduceGenerator);
await asyncTest('reducePromise: should sum a list of numbers using promises', reducePromise);
await asyncTest('reducePromiseProxy: should sum a list of numbers using promises proxied to the reduce sync', reducePromiseProxy);
await asyncTest('reducePromiseRecurse: should sum a list of numbers using promises', reducePromiseRecurse);
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment