For those who read my previous articles, you might know my fascination for the Array.prototype method reduce
(Click here to find out more about this function).
Talking with a colleague of mine, we actually realised that it was so flexible we could probably implement all the other Array.prototype methods using it instead. Let's try that together in this article!
Quick overview of what we will do:
- Only write pure functions. It is a "must do" if you interested in declarative and functional programming and I find
reduce()
really helpful when you go on that route. - Re-implement all the accessor and iteration methods. Not that I'm not interested in mutations but I don't use them on regular basis (see 1.).
- Only use one
reduce
.
But first, I would really advise you to DO IT YOURSELF. You don't really have to write them all down but I found this exercise really interesting for few reasons.
- It is a great way to learn
reduce()
if you're not too familiar with it. - I found myself rediscovering some
Array.prototype
methods likesome
that I haven't used for ages and could be really interesting to use in my day to day code.
Accessor methods
Array.prototype.length
The
length
property of an object which is an instance of type Array sets or returns the number of elements in that array. The value is an unsigned, 32-bit integer that is always numerically greater than the highest index in the array.
Yes, I know Array.prototype.length
is a property. I still find this property quite magic since - unlike most other properties you would find - it automatically mutates based on the length of your array and therefore could belong to this analysis.
const length = arr => () => arr.reduce((acc) => acc + 1, 0);
Array.prototype.concat()
The
concat()
method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
const concat = arr1 => arr2 => arr2.reduce((acc, el) => [...acc, el], arr1);
Array.prototype.includes()
The
includes()
method determines whether an array includes a certain value among its entries, returningtrue
orfalse
as appropriate.
const includes = arr => element => arr.reduce((acc, el) => acc || (el === element), false);
Array.prototype.indexOf()
The
indexOf()
method returns the first index at which a given element can be found in the array, or -1 if it is not present.
const indexOf = arr => element => arr.reduce((acc, el, index) => (
(acc === -1 && el === element) ? index : -1),
-1,
);
Array.prototype.join()
The
join()
method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
const join = arr => (separator = '') => arr.reduce((acc, el) => `${acc}${separator}${el}`);
Array.prototype.lastIndexOf()
The
lastIndexOf()
method returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex.
const lastIndexOf = arr => element => arr.reduce((acc, el, index) => (el === element ? index : -1), -1);
Array.prototype.slice()
The
slice()
method returns a shallow copy of a portion of an array into a new array object selected frombegin
toend
(end not included) where begin and end represent the index of items in that array. The original array will not be modified.
const isBetween = i => (begin, end) => i >= begin && i < end;
const slice = arr => (begin, end) => arr.reduce((acc, el, index) => isBetween(index)(begin, end) ? [...acc, el] : acc, []);
Array.prototype.toString()
The toString() method returns a string representing the specified array and its elements.
const toString = arr => () => arr.reduce((acc, el) => `${acc},${el}`);
Array.prototype.toLocaleString()
The toLocaleString() method returns a string representing the elements of the array. The elements are converted to Strings using their toLocaleString methods and these Strings are separated by a locale-specific String (such as a comma “,”).
const toLocaleString = arr => () => arr.reduce((acc, el, index) => `${acc}${index === 0 ? '' : ','}${el.toLocaleString()}`, '');
Iteration methods
Array.prototype.entries()
The
entries()
method returns a new Array Iterator object that contains the key/value pairs for each index in the array.
const entries = arr => () => arr.reduce((acc, el, index) => [...acc, [index, el]], []);
Array.prototype.every()
The
every()
method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.Note: This method returns true for any condition put on an empty array.
const every = arr => predicate => arr.reduce((acc, ...rest) => acc && predicate(...rest), true);
Array.prototype.filter()
Definition: The
filter()
method creates a new array with all elements that pass the test implemented by the provided function.
const every = arr => predicate => arr.reduce((acc, el, ...rest) => (predicate(el, ...rest) ? [...acc, el] : acc), []);
Array.prototype.find()
Definition: The find() method returns the value of the first element in the provided array that satisfies the provided testing function.
This one is quite tricky: my first reaction was to write down this code.
const find = arr => predicate => arr.reduce(
(acc, el, index, array) => (acc === undefined && predicate(el, index, array)) ? el : undefined,
undefined,
);
Then I realised an edge case that would actually make this function behave differently from the original find
method. Here is the edge case:
console.log([undefined, null].find(el => !el)); // Returns: undefined
console.log(find([undefined, null])(el => !el)) // Returns: null
How to solve this edge case? Well looks like our only source of truth will be the index of the found element. Let's try that one instead:
const find = arr => predicate => arr.reduce(
(acc, el, index, array) => (acc[1] === -1 && predicate(el, index, array)) ? [el, index] : acc,
[undefined, -1],
)[0];
This way we are sure that the first element that is found will be returned by our function.
Array.prototype.findIndex()
Definition: The
findIndex()
method returns the index of the first element in the array that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test.
This one should be quite straight forward considering the code written above
const findIndex = arr => predicate => arr.reduce(
(acc, el, index, array) => (acc[1] === -1 && predicate(el, index, array)) ? [el, index] : acc,
[undefined, -1],
)[1];
Array.prototype.forEach()
Definition: The
forEach()
method executes a provided function once for each array element.
const forEach = arr => callback => arr.reduce(
(_, ...rest) => { callback(...rest); },
[],
);
Array.prototype.keys()
Definition: The
keys()
method returns a newArray Iterator
object that contains the keys for each index in the array.
const keys = arr => () => arr.reduce((acc, _, index) => [...acc, index], []);
Array.prototype.map()
Definition: The
map()
method creates a new array with the results of calling a provided function on every element in the calling array.
const map = arr => callback => arr.reduce((acc, ...rest) => [...acc, callback(...rest)], []);
Array.prototype.some()
Definition: The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.
Note: This method returns false for any condition put on an empty array.
const some = arr => predicate => arr.reduce((acc, ...rest) => acc || predicate(el, ...rest), false);
Array.prototype.values()
Definition: The values() method returns a new Array Iterator object that contains the values for each index in the array.
const values = arr => () => arr.reduce((acc, el) => [...acc, el], []);
Array.prototype.reduceRight()
Definition: The reduceRight() method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.
For this one, my first idea was obviously to reverse the array and then apply a reduce()
. Lol funny but not optimal as we are iterating twice on the array 🙃.
const reduceRight = arr => (...params) => arr.reduce((acc, el) => [el, ...acc]).reduce(...params);
After searching for ages, I must say I'm quite disappointed on this one... I have not been able to find a short and clean way to implement reduceRight()
with only one reduce()
... It is extremely frustrating and I would love to hear someone who has a suggestion regarding it!
Conclusion
Among a lot of trivial methods to implement, several one have been interesting to implement:
-
find()
: this edge-case actually made the implementation a bit stiffer than I thought. It really pushed me to have a critical eye on when it comes to the predicates I'm using. -
reduceRight()
: this is actually my biggest frustration. I would be really interested to know if one of you can solve it and how to!
Top comments (0)