DEV Community

Cover image for How map() and reduce() Array method works in JavaScript.
Swastik Yadav
Swastik Yadav

Posted on • Edited on

How map() and reduce() Array method works in JavaScript.

Hello Everyone,

Today let's implement our own version of the map() and reduce() array methods in JavaScript.

The actual implementation of the inbuilt array methods are way more complex than this. What we are aiming towards is to understand the Array methods better.

myMap()

First let's understand what does the built in map method do.

js-map

  • Takes an array as input -> returns an array as output.
  • Iterates over all the elements of array.
  • Returns an array of same length as the original array.
  • Does not mutate the original array.

Here is our implementation of the map() Array method.

const numArr = [1, 2, 3, 4];

function myCustomMap(callback) {
  const newArr = [];
  for(let i = 0; i < this.length; i++) {
    newArr.push(callback(this[i], i, this));
  }

  return newArr;
}

Array.prototype.myMap = myCustomMap;

numArr.map(elm => elm * 2); // [2, 4, 6, 8]
numArr.myMap(elm => elm * 2); // [2, 4, 6, 8]
Enter fullscreen mode Exit fullscreen mode

Let's see what's going on in the above snippet.

  1. myCutomMap function takes a callback (this callback is the function we write in map).
  2. We loop over all elements of array.
  3. The callback function is called with the current element and the returned value is pushed in the newArr.
  4. newArr is returned from the myCustomMap function.
  5. We add the myCustomMap function in Array.prototype, so that we can use it with dot notation.
  6. The "this" keyword in myCustomMap refers to the array on which the myCustomMap is used with dot notation. (numArr.myMap((elm) => elm * 2) // here "this" refers to numArr).

And, that's how you build your own custom map() Array method.

myReduce()

Now let's understand what does the built in reduce method do.

reduce-array-method

  • Takes an array -> Returns any type of data you want.
  • Iterates over all the elements of the array.
  • Reduces the array to any datatype you want.
  • Does not mutate the original array.
const personArray = [{name: "Swastik", age: 23}, {name: "John", age: 42}, {name: "Rock", age: 45}];

function myCustomReduce(callback, initialValue) {
  for(let i = 0; i < this.length; i++) {
    initialValue = callback(initialValue, this[i], i, this);
  }

  return initialValue;
}

Array.prototype.myReduce = myCustomReduce;

personArray.reduce((acc, cv) => {
  acc = {
    ...acc,
    [cv.name]: cv.name.length,
  };
  return acc;
}, {}); // {Swastik: 7, John: 4, Rock: 4}

personArr.myReduce((acc, cv) => {
  acc = {
    ...acc,
    [cv.name]: cv.name.length,
  };
  return acc;
}, {}); // {Swastik: 7, John: 4, Rock: 4}
Enter fullscreen mode Exit fullscreen mode

Now let's see what's going on in the myCustomReduce snippet.

We want to reduce the following array

[{name: "Swastik", age: 23}, {name: "John", age: 42}, {name: "Rock", age: 45}]
Enter fullscreen mode Exit fullscreen mode

to and object with key as name and value as length of the name

{Swastik: 7, John: 4, Rock: 4}
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. myCutomReduce function takes a callback and an initialValue (this initialValue is the datatype which we want the reduce to return).
  2. We loop over all elements of array.
  3. The callback function is called with the current element and the returned value is assigned to the initialValue. With every iteration initialValue is updated.
  4. initialValue is returned from the myCustomReduce function.
  5. We add the myCustomReduce function in Array.prototype, so that we can use it with dot notation.
  6. The "this" keyword in myCustomReduce refers to the array on which the myCustomReduce is used with dot notation.

And, tha't how you build your own version of the reduce Array method in JavaScript.


I had a great time writing and recording about this topic, I hope you felt the same while reading and watching this.

I also run a weekly newsletter, so you can join me there also: https://swastikyadav.com

Thank You!

Top comments (4)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️
personArr.myReduce((acc, cv) => {
  acc = {
    ...acc,
    [cv.name]: cv.name.length,
  };
  return acc;
}, {}); // {Swastik: 7, John: 4, Rock: 4}
Enter fullscreen mode Exit fullscreen mode

Why the in-between variable? You could just write the function as (acc, cv) => {...acc, [cv.name]: cv.name.length}

Or, if you don't mind the in-between variable, at least make better use of it and mutate the old object instead of copying it entirely

personArr.myReduce((acc, cv) => {
  acc[cv.name] = cv.name.length
  return acc
}, {}) // {Swastik: 7, John: 4, Rock: 4}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
swastikyadav profile image
Swastik Yadav

In btwn variable is to keep things as soon so simple as possible because this is a blog post.

But yes, nice enhancement. Thanks for sharing.

Collapse
 
peerreynders profile image
peerreynders • Edited

For me the canonical way of learning about map and reduce is to

  1. use recursion
  2. implement foldl (reduce) using recursion
  3. implement map in terms of foldl

Now in JavaScript this isn't terribly efficient …

const personArray = [
  { name: 'Swastik', age: 23 },
  { name: 'John', age: 42 },
  { name: 'Rock', age: 45 },
];

type Person = typeof personArray[number];

// reduce
function foldl<X, Y>(fn: (y: Y, x: X) => Y, to: Y, from: X[]): Y {
  return from.length < 1 ? to : foldl(fn, fn(to, from[0]), from.slice(1));
}

function map<X, Y>(fn: (x: X) => Y, from: X[]): Y[] {
  const append: (ys: Y[], x: X) => Y[] = (ys, x) => {
    ys.push(fn(x));
    return ys;
  };
  return foldl(append, [], from);
}

function rmap<X, Y>(fn: (x: X) => Y, from: X[]): Y[] {
  // return from.length < 1 ?
  //   [] :
  //   [fn(from[0]), ...rmap(fn, from.slice(1))];
  //
  if (from.length < 1) return [];

  const rest = rmap(fn, from.slice(1));
  rest.unshift(fn(from[0]));
  return rest;
}

function filter<X>(fn: (x: X) => boolean, from: X[]): X[] {
  const appendAccept: (xs: X[], x: X) => X[] = (xs, x) => {
    if (fn(x)) xs.push(x);
    return xs;
  };
  return foldl(appendAccept, [], from);
}

function rfilter<X>(fn: (x: X) => boolean, from: X[]): X[] {
  // return from.length < 1 ?
  //   [] :
  //   fn(from[0]) ?
  //     [from[0], ...rfilter(fn, from.slice(1))] :
  //     rfilter(fn, from.slice(1));
  //
  if (from.length < 1) return [];

  const rest = rfilter(fn, from.slice(1));
  if (fn(from[0])) rest.unshift(from[0]);
  return rest;
}

type People = {
  [name: string]: number;
};

const appendPerson: (a: People, p: Person) => People = (people, person) => {
  people[person.name] = person.age;
  return people;
};

console.log(foldl(appendPerson, {}, personArray));

const extractName: (p: Person) => string = (person) => person.name;

console.log(map(extractName, personArray));
console.log(rmap(extractName, personArray));

const inUpperAlphabet: (p: Person) => boolean = (person) =>
  person.name[0].toLowerCase() > 'm';

console.log(filter(inUpperAlphabet, personArray));
console.log(rfilter(inUpperAlphabet, personArray));
Enter fullscreen mode Exit fullscreen mode

This transpiles down to

const personArray = [
  { name: 'Swastik', age: 23 },
  { name: 'John', age: 42 },
  { name: 'Rock', age: 45 },
];
// reduce
function foldl(fn, to, from) {
  return from.length < 1 ? to : foldl(fn, fn(to, from[0]), from.slice(1));
}
function map(fn, from) {
  const append = (ys, x) => {
    ys.push(fn(x));
    return ys;
  };
  return foldl(append, [], from);
}
function rmap(fn, from) {
  if (from.length < 1) return [];
  const rest = rmap(fn, from.slice(1));
  rest.unshift(fn(from[0]));
  return rest;
}
function filter(fn, from) {
  const appendAccept = (xs, x) => {
    if (fn(x)) xs.push(x);
    return xs;
  };
  return foldl(appendAccept, [], from);
}
function rfilter(fn, from) {
  if (from.length < 1) return [];
  const rest = rfilter(fn, from.slice(1));
  if (fn(from[0])) rest.unshift(from[0]);
  return rest;
}
const appendPerson = (people, person) => {
  people[person.name] = person.age;
  return people;
};
console.log(foldl(appendPerson, {}, personArray));
const extractName = (person) => person.name;
console.log(map(extractName, personArray));
console.log(rmap(extractName, personArray));
const inUpperAlphabet = (person) => person.name[0].toLowerCase() > 'm';
console.log(filter(inUpperAlphabet, personArray));
console.log(rfilter(inUpperAlphabet, personArray));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jonrandy profile image
Jon Randy 🎖️

initialValue should be optional