Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array map with accumulator in JavaScript

How to use Array map with accumulator?

Lets have a list of numbers and find a list of current sums. Example:

const nums = [1, 1, 1, -1, -1];
const sums = [1, 2, 3,  2,  1];

I try to do it with map, by using accumulator in a thisArg because according to: MDN Array.prototype.map()

thisArg - value to use as this when executing callback.

I provide an object with acc set to 0 as thisArg:

const actual = nums.map(val => this.acc += val, {acc: 0});

require('assert').deepEqual(actual, sums);

It crashes with error:

AssertionError: [ 1, 2, 3, 2, 1 ] deepEqual [ NaN, NaN, NaN, NaN, NaN ]

The test passes with an external accumulator:

let   acc    = 0;
const actual = nums.map(val => acc += val);
like image 687
Miroslav Popov Avatar asked Dec 25 '17 11:12

Miroslav Popov


People also ask

What is accumulator in JavaScript?

accumulator is the value returned from the previous iteration. It will be initialValue for the first iteration. currentValue is array item in the current iteration.

Can I use map and filter together JavaScript?

Filtering first, and then mapping, can be accomplished in one line! In this example, we filter by items that include the word “coffee” and then map around the filtered array to create an array with only the names. It's a great way to make code look cleaner and operate more efficiently. We could do map and then filter!

How do you use map and reduce together?

Using reduce and map() together Here's how it's done: Map the array into an array of zeros and ones. Reduce the array of zeros and ones into the sum.

How do you map an array in JavaScript?

The syntax for the map() method is as follows: arr. map(function(element, index, array){ }, this); The callback function() is called on each array element, and the map() method always passes the current element , the index of the current element, and the whole array object to it.


2 Answers

With using arrow functions, you loose this in the function, which is already set from the outer space.

You could use a function statement and thisArg.

const nums = [1, 1, 1, -1, -1];
const actual = nums.map(function (val) { return this.acc += val; }, { acc: 0 });

console.log(actual);

For keeping a arrow function, you could use a closure over the accumulator,

(acc => val => acc += val)(0)  // complete closure with callback

which works in two steps, first it calls the function directly with a value for acc

(acc =>                  )(0)  // function for generating a closure over acc

and returns the inner function as a callback for Array#map

        val => acc += val      // final callback

with a closure over acc, that means the scope of acc is inside of the own function and inside of the returned callback.

const nums = [1, 1, 1, -1, -1];
const actual = nums.map((acc => val => acc += val)(0));

console.log(actual);
like image 197
Nina Scholz Avatar answered Sep 24 '22 18:09

Nina Scholz


You can use Array.prototype.reduce() instead. This will not require you to create additional closure for arrow function and provide accumulator as regular argument.

const nums = [1, 1, 1, -1, -1]
const actual = nums.reduce(
    (acc, val) => (acc.push((acc[acc.length-1] || 0) + val), acc), []
)

console.log(actual) // [1, 2, 3, 2, 1]

Edit:

If you concerned about performance, here is a jsPerf for comparison with your's and @Nina's options:

  • .map() with standard function - 27% slower in Firefox and 5% faster in Chrome than .map() with factory function
  • .reduce() is 94% slower in Firefox and 52% slower in Chrome than fastest of two above.
  • your own option (with external accumulator) is 75% faster than .map() with standard function!
like image 37
Vadim Avatar answered Sep 21 '22 18:09

Vadim