loading...
Cover image for Exercise - Array.prototype methods with reduce

Exercise - Array.prototype methods with reduce

marwaneb profile image Marwan El Boussarghini ・7 min read

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:

  1. 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.
  2. 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.).
  3. 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 like some 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

Link

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()

Link

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()

Link

The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.

const includes = arr => element => arr.reduce((acc, el) => acc || (el === element), false);

Array.prototype.indexOf()

Link

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()

Link

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()

Link

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()

Link

The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (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()

Link

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()

Link

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()

Link

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()

Link

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 new Array 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!

Posted on by:

marwaneb profile

Marwan El Boussarghini

@marwaneb

Full stack Engineer at Third Bridge - London UK 🇬🇧 From Rennes, France 🇫🇷 CentraleSupelec Alumni 🎓 Metal music enthusiast 🤘🏽

Discussion

markdown guide