accumulator returns undefined within making `reduce` method

Viewed 813

I tried to make a function working same as Array.prototype.reduce method.

function a(collection, iterator, accumulator) {

    for (i of Object.keys(collection)){
      if (i===0 && accumulator === undefined){
          accumulator = collection[i]
          continue
      }

      accumulator = iterator(accumulator, collection[i])
    }

    return accumulator;
  };



a([1,2,3],function(acc,cur){return acc += cur}, 0) // this works fine. returns 6
a([1,2,3],function(acc,cur){return acc += cur}) // expected 6 but returns NaN
a([1,2,3], function(memo){return memo}); // expected 1 but returns undefined

I don't know why accumulator does not set as collection[i] and keep stayed as undefined when the initial value (the third parameter of function a) not delivered.

1 Answers

Object.keys always returns an array of strings, so i is a string, so i === 0 is always false.

Also note that you need to declare i. Right now, your code is falling prey to The Horror of Implicit Globals. (I suggest using strict mode so they're the error they always should have been.)

FWIW, here's the minimum update addressing both of those (but see below as well):

"use strict";
function a(collection, iterator, accumulator) {
    let first = true;                            // ***
    for (const i of Object.keys(collection)){
    //   ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− *** declare `i`
        if (first && accumulator === undefined){
    //      ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− *** first pass?
            first = false;                       // ***
            accumulator = collection[i]
            continue
        }
        first = false;                           // ***

        accumulator = iterator(accumulator, collection[i])
    }

    return accumulator;
};

console.log(a([1,2,3],function(acc,cur){return acc += cur}, 0));
console.log(a([1,2,3],function(acc,cur){return acc += cur}));
console.log(a([1,2,3], function(memo){return memo}));


FWIW, I don't know if you meant it to be an exact match for reduce, but if so the logic of your function is not the same as the logic of Array.prototype.reduce. See the specification for details, but reduce checks how many arguments it got, it doesn't check to see if the accumulator is undefined. If you explicitly give it the value undefined for the accumulator, it will use that value (rather than using the first array element's value):

console.log("`undefined` for accumulator:");
["a", "b", "c"].reduce((acc, value) => { console.log(acc, value); return value; }, undefined);

console.log("No accumulator:");
["a", "b", "c"].reduce((acc, value) => { console.log(acc, value); return value; })
.as-console-wrapper {
    max-height: 100% !important;
}

The accumulator can also become undefined during the reduction. And if there's no accumulator given, it's the first value seen (not necessarily at index 0 in a sparse array!) that's used. (My fix for the accumulator above actually fixes that as a by-product.) Your code also uses Object.keys whereas reduce just uses a for loop. I didn't do a close read, there could be other discrepancies as well.

"use strict";
function a(collection, iterator, ...optional) {  // ***
    let haveAccumulator = optional.length > 0;   // *** Default acc correctly
    let [accumulator] = optional;                // ***
    for (const i of Object.keys(collection)){
    //   ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− *** declare `i`
        if (!haveAccumulator) {                  // ***
            haveAccumulator = true;              // ***
            accumulator = collection[i]
            continue;
        }

        accumulator = iterator(accumulator, collection[i])
    }

    return accumulator;
};



console.log(a([1,2,3],function(acc,cur){return acc += cur}, 0));
console.log(a([1,2,3],function(acc,cur){return acc += cur}));
console.log(a([1,2,3], function(memo){return memo}));

Related