1252748
1252748

Reputation: 15369

how to access zeroth element in reduce to count repeats in an array

On the whim of node school, I am trying to use reduce to count the number of times a string is repeated in an array.

var fruits = ["Apple", "Banana", "Apple", "Durian", "Durian", "Durian"],
    obj = {};
fruits.reduce(function(prev, curr, index, arr){
   obj[curr] ? obj[curr]++ : obj[curr] = 1;
});
console.log(obj); // {Banana: 1, Apple: 1, Durian: 3}

is sort of working. For some reason, reduce seems to skip the first element. I don't know why. Its first time through the array, index is 1. I tried putting in some logic like, if (index === 1){//put 'prev' as a property of 'obj'}. But that seems really convoluted. I'm certain that this is not how node school wants me to solve this problem. However, I wonder what's a good way to access the zeroth element in the array you're reducing. Why is this zeroth element seemingly ignored by the reduction procedure? I guess I could pass in fruits[0] after the callback so I start with that value initially. What's the best way to access this zeroth element?

Upvotes: 12

Views: 11670

Answers (4)

faboBelMe
faboBelMe

Reputation: 1

accordin whit the official documentation, you should set the initial value.

var fruits = ["Apple", "Banana", "Apple", "Durian", "Durian", "Durian"];
fruits.reduce(function(prev, curr, index, arr){
    //...
}, 0);
The initial value was cero(0).

console.log(obj); // {Banana: 1, Apple: 2, Durian: 3}

I took this from documentation:

initialValue Optional A value to which accumulator is initialized the first time the callback is called. If initialValue is specified, callbackFn starts executing with the first value in the array as currentValue. If initialValue is not specified, accumulator is initialized to the first value in the array, and callbackFn starts executing with the second value in the array as currentValue. In this case, if the array is empty (so that there's no first value to return as accumulator), an error is thrown.

Upvotes: 0

Stephen Quan
Stephen Quan

Reputation: 26141

The syntax of reduce() is:

arr.reduce( callback [, initial] );

If you omit initial value, callback will get called 5 times, with arr[0] passed in as the initial value of previous:

callback( previous=arr[0], current=arr[1], index=0, array=arr );
callback( previous       , current=arr[2], index=1, array=arr );
callback( previous       , current=arr[3], index=2, array=arr );
callback( previous       , current=arr[4], index=3, array=arr );
callback( previous       , current=arr[5], index=4, array=arr );

If you include initial value, callback will get called 6 times with initial passed in as the initial value of previous:

callback( previous=initial, current=arr[0], index=0, array=arr );
callback( previous        , current=arr[1], index=1, array=arr );
callback( previous        , current=arr[2], index=2, array=arr );
callback( previous        , current=arr[3], index=3, array=arr );
callback( previous        , current=arr[4], index=4, array=arr );
callback( previous        , current=arr[5], index=5, array=arr );

Because you're building an object, I recommend you passed in an empty object { } as the initial value:

const fruits = [ "Apple", "Banana", "Apple", "Durian", "Durian", "Durian" ];
const initial = { };
const result = fruits.reduce( function (previous, current, index, array) {
        previous[current] = !previous[current] ? 1 : previous[current] + 1;
        return previous;
    },
    initial
);
console.log( "result: ", result );

// Output:
//
// result:  {
//   "Apple": 2,
//   "Banana": 1,
//   "Durian": 3
// }

Reference:

Upvotes: 9

Sadid Khan
Sadid Khan

Reputation: 1985

This question is quite old and the answer is already given to the point.

In addition with that I am including the description from offical Documentation that has some other key point and it will be helpful for the new comers

From the official documentation of Array.prototype.reduce()

The reduce() method executes the callback once for each assigned value present in the array, taking four arguments:

  • accumulator
  • currentValue
  • currentIndex
  • array

The first time the callback is called, accumulator and currentValue can be one of two values. If initialValue is provided in the call to reduce(), then accumulator will be equal to initialValue, and currentValue will be equal to the first value in the array. If no initialValue is provided, then accumulator will be equal to the first value in the array, and currentValue will be equal to the second.

Note: If initialValue is not provided, reduce() will execute the callback function starting at index 1, skipping the first index. If initialValue is provided, it will start at index 0.

If the array is empty and no initialValue is provided, TypeError will be thrown.

If the array only has one element (regardless of position) and no initialValue is provided, or if initialValue is provided but the array is empty, the solo value will be returned without calling callback.

And also a bit modified one,

var fruits = ["Apple", "Banana", "Apple", "Durian", "Durian", "Durian"], obj = {};
fruits.reduce((prev, curr, index, arr) => {
   if(!prev.hasOwnProperty(curr)) {
       prev[curr] = 0;
   }
   prev[curr]++;
   return prev;
}, obj);
console.log(obj);

Upvotes: 0

Joseph
Joseph

Reputation: 119867

If no initialValue was provided, then previousValue will be equal to the first value in the array and currentValue will be equal to the second.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Description

Additionally, you have to return a value from the function. That value becomes the value of previousValue on the next iteration.

I'd suggest you "carry" your aggregator obj as the initial value.

var fruits = ["Apple", "Banana", "Apple", "Durian", "Durian", "Durian"];
var obj = fruits.reduce(function(carry, fruit){
  if(!carry[fruit]) carry[fruit] = 0; // If key doesn't exist, default to 0
  carry[fruit]++;                     // Increment the value of the key
  return carry;                       // Return aggregator for next iteration
}, {});
alert(JSON.stringify(obj));

Here's a simple diagram:

               fruit  carry (before operation)      carry (after operation, returned value)
1st iteration: Apple  {}                            {Apple:1}
2nd iteration: Banana {Apple:1}                     {Apple:1, Banana:1} 
3rd iteration: Apple  {Apple:1, Banana:1}           {Apple:2, Banana:1}
4th iteration: Durian {Apple:2, Banana:1}           {Apple:2, Banana:1, Durian:1}
5th iteration: Durian {Apple:2, Banana:1, Durian:1} {Apple:2, Banana:1, Durian:2}
6th iteration: Durian {Apple:2, Banana:1, Durian:2} {Apple:2, Banana:1, Durian:3}

Upvotes: 16

Related Questions