skip to Main Content

I am working on an exercise that is based on the reduce exercise from underscore.js. Now, I have a solution that works but I believe it can be done better since I used a lot of copy pasting in my Frankenstein solution.

The Solution I found

This is the solution I have come up with:

_.reduce = function (collection, iteratee, accumulator, context) {
  if (Array.isArray(collection)) {
    var returnVal = accumulator !== undefined ? accumulator : collection[0];
    if (accumulator !== undefined) {
      for (var i = 0; i < collection.length; i++) {
        returnVal = iteratee.call(
          context,
          returnVal,
          collection[i],
          i,
          collection
        );
      }
    } else {
      for (var i = 1; i < collection.length; i++) {
        returnVal = iteratee.call(
          context,
          returnVal,
          collection[i],
          i,
          collection
        );
      }
    }
    console.log(returnVal);
    return returnVal;
  } else if (typeof collection === "object") {
    var keys = Object.keys(collection);
    var returnVal =
      accumulator !== undefined ? accumulator : collection[keys[0]];
    if (accumulator !== undefined) {
      for (var i = 0; i < keys.length; i++) {
        returnVal = iteratee.call(
          context,
          returnVal,
          collection[keys[i]],
          keys[i],
          collection
        );
      }
    } else {
      for (var i = 1; i < keys.length; i++) {
        returnVal = iteratee.call(
          context,
          returnVal,
          collection[keys[i]],
          keys[i],
          collection
        );
      }
    }
    console.log(returnVal);
    return returnVal;
  }
};

As you can see, I distinguish between a collection that is an array, and a collection that is an object. This is my first conditional. Within this conditional I have another conditional for both the array and the object. This conditional checks whether accumulator is not undefined. If true, it runs the for loop from the first iteration, if false, it runs the for loop from the second iteration. I do this because when accumulator is undefined, it has to be the first value in the collection, and if I would then run the for loop from the first iteration, the first value would appear double.

What I have tried

I have tried adding a conditional inside the for loop to check if accumulator is undefined, if it was undefined I would continue. This however did not work because accumulator stays undefined obviously.

I also tried to add that conditional in the for loop and start the for loop from the second iteration, and the conditional would add the first value of the collection to returnVal manually (if accumulator is undefined). This also did not work because it messed up the results and gave it back in the wrong order.

What I am expecting

I don’t know if it is possible, but I would like to shorten my code to make it more readable, and just better code in general. I think it is possible to shorten this code, I just can’t see where and how. If there are any other improvements to be made, I would gladly hear about them to as it will help me become a better coder!!

Thanks in advance.

ps. I cannot use built-in functions like forEach(), map(), reduce() and filter().

2

Answers


  1. Here a simplified code. It’s used a single for loop by setting the initial index i based on whether accumulator is undefined or not. If accumulator is undefined, it starts from index 1, skipping the first iteration. If accumulator is defined, it will start from index 0, including the first iteration. And it’s also better to use let instead of var.

    _.reduce = function (collection, iteratee, accumulator, context) {
        let returnVal, i, keys;
        
        if (Array.isArray(collection)) {
          returnVal = accumulator !== undefined ? accumulator : collection[0];
          i = accumulator !== undefined ? 0 : 1;
          
          for (; i < collection.length; i++) {
            returnVal = iteratee.call(context, returnVal, collection[i], i, collection);
          }
          
          return returnVal;
        } 
        
        if (typeof collection === "object") {
          keys = Object.keys(collection);
          returnVal = accumulator !== undefined ? accumulator : collection[keys[0]];
          i = accumulator !== undefined ? 0 : 1;
          
          for (; i < keys.length; i++) {
            returnVal = iteratee.call(context, returnVal, collection[keys[i]], keys[i], collection);
          }
          
          return returnVal;
        }
      };
    
    Login or Signup to reply.
  2. In the true spirit of functional programming, you can reduce (no pun intended) your code dramatically by building on top of code that already does roughly what you want. What you want is to iterate either an object or an array (or any other type of collection that Underscore might support in the future), and _.find and _.each already do exactly that.

    As for treating the first iteration specially, a state variable works very well in this case. This is what such an approach might look like:

    function reduce(collection, iteratee, accumulator, context) {
        // The state variable, boolean
        var specialFirst = (accumulator == undefined);
        _.find(collection, function(value, key) {
            if (specialFirst) {
                accumulator = value;
                specialFirst = false;
            } else {
                accumulator = iteratee.call(context, accumulator, value, key, collection);
            }
        }, context);
        return accumulator;
    }
    
    function add(left, right) {
        return left + right;
    }
    
    console.log(reduce([1, 2, 3], add));
    console.log(reduce([1, 2, 3], add, 2));
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/underscore-umd-min.js"></script>

    (I have no idea why that script error at the end appears, but it is probably specific to the snippet runner.)

    Coincidentally, I have worked on a branch in Underscore where I did something similar, but more sophisticated. The iteratee changes identity temporarily in order to achieve the same end result, so you could say that the iteratee itself is the state variable. You can read it here: https://github.com/jashkenas/underscore/blob/eaba5b58fa8fd788a5be1cf3b66e81f8293f70f9/modules/_createReduce.js.

    Greetings from the current Underscore maintainer, I wish you much luck discovering Underscore and functional programming!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search