Javascript run length encoding algorithm I have implemented a custom run length encoding algorithm that converts an m x n array into an p x (n+1) array. The extra column is to record counts. Probably easier to show through examples:



encode = (array) =&gt; {
  // crude array equality function
  arrayEquals = (...arr) =&gt; {
    return arr[0].map(x =&gt; {
      return JSON.stringify(x);
    }).every((x, _, a) =&gt; {
      return x === a[0];
    });
  };

  let result = [],
    count = -1,
    len = array.length;

  array.reduce((acc, val, i) =&gt; {
    if (!arrayEquals([acc, val])) {
      // if current value differs from last
      if (i !== len - 1) {
        // push the count and data to the result
        result.push([++count, acc]);
        count = 0; // reset the count
      } else { // last iteration
        // push the (n - 1)th  value
        result.push([++count, acc]);
        // now push the nth value
        result.push([1, val]);
      }
    } else {
      // if current value equals the last, increment counter and continue
      count++;
      if (i === len - 1) { // last iteration
        // push regardless 
        count === 1 ? result.push([++count, val]) : result.push([count, val]);
      }
    }
    return val;
  }, array[0]);

  return result;
};

console.log(encode([1, 1, 2, 2, 3, 3, 4]));
console.log(encode([
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [10, 11, 12]
]));
console.log(encode([
  [1, 2, 3],
  [1, 2, 3],
  [4, 5, 6]
]));
console.log(encode([
  [0],
  [0],
  [1],
  [2, 3],
  [4, 5, 6],
  [],
  []
]));




I think I've tried the full range of common test cases and it has worked so far for all of them but I feel there must be a better way to implement this. The logic was awkward to write given the need to have knowledge of previous iterations and the current. I used reduce as it seemed to lend itself to this but even so, keeping track of when to increment was very difficult and doesn't make for the most readable code. My hunch is that there's a neat solution where one adds dummy elements to the beginning or end of the initial array and then can do away with the need for the (if (i === len - 1) logic)?

Any improvements would be much appreciated.
Separation of concerns

The problem has two distinct, independent components:


Compute run length
Decide if two values are equal


I agree that using reduce makes sense here to compute the run length.
If you separate this computation from the equality decision,
the logic of the reducer can become a lot simpler:

  const reducer = (acc, val) =&gt; {
    if (acc.length &gt; 0 &amp;&amp; equals(acc[acc.length - 1][1], val)) {
      acc[acc.length - 1][0]++;
      return acc;
    }
    acc.push([1, val]);
    return acc;
  };
  return arr.reduce(reducer, []);


That is:


When the accumulator is not empty, and the last element has matching value,
then increment the count of the last element, and return the accumulator.
Append [1, val] to the accumulator.
The accumulator starts empty.


The equality condition is to be implemented.
You can even require the implementation to be passed in as parameter.

As @radarbob suggests,
it's good to get the run-length computation working without worrying about mixed value types.
The implementation of the equals function here is not interesting at all for run-length computation.
You can start with a simple const equals = (a, b) =&gt; a === b.
Having mixed values in the input doesn't affect at all the run-length computation logic.

The converse is also true: it should be possible to implement the equality logic independently from run-length.
This reduces your mental burden and helps you think clearly,
focusing on one problem at a time.
I came up with this:

  const equals = (a, b) =&gt; {
    if (a === b) {
      return true;
    }
    if (typeof(a) !== typeof(b)) {
      return false;
    }
    if (Array.isArray(a)) {
      return arrayEquals([a, b]);
    }
    return false;
  };


This implementation handles some easy basic cases,
and delegates some non-trivial ones,
such as comparing arrays.
Your arrayEquals will work nicely for the purpose.
Looking at this implementation,
it's also quite clear what kind of values are supported:
primitive and arrays only.
(As far as we can trust the externalized arrayEquals implementation...)