how to access zeroth element in reduce to count repeats in an array
Asked Answered
D

4

12

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?

Dabchick answered 16/7, 2015 at 22:16 Comment(4)
The first element is held by prev because you didn't start with a seed. In subsequent calls, prev will hold the previous return value, but you're returning nothing.Monique
The way you're doing it, you should be using .forEach().Monique
@LyeFish you should add that as an answer (and show a "working solution")Dunsinane
The reduce function works in a bit another way. It iterates over an array, aggregating items into a total value. The first argument in callback function is actually a total, not previous item. So, during the first iteration the callback is not called because total = current item. You should use another function to iterate over all items, e.g. here: #3011340Ianteen
L
16

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}
Loveridge answered 16/7, 2015 at 22:26 Comment(2)
why do you pass it that empty object at the end?Dabchick
@thomas The value of carry in the next iteration is what is returned by the function during the previous iteration. For the very first iteration (where there's no previous), that object at the end is carry. In context to your code, it works like obj. What my code just did is pass around that object and collect the count.Loveridge
J
9

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:

Jugum answered 16/7, 2015 at 23:14 Comment(0)
M
0

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);
Mafalda answered 2/4, 2020 at 19:19 Comment(0)
C
0

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.

Challis answered 11/4, 2023 at 16:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.