6
\$\begingroup\$

I have got this interview question which has asked me to write a production level code which flattens the arbitrary nested array of arrays.

Code

/**
 * Flattens the array of arrays.
 *
 * @param {!Array<?>} elements The nested array elements.
 * @return {!Array<?>} The flat list of array objects.
 */
function flatten(elements) {
  return elements.reduce(function(result, current) {
    if (Array.isArray(current)) {
      return result.concat(flatten(current));
    }
    return result.concat(current);
  }, []);
};

/**
 * Compares two arrays.
 *
 * @param {!Array<T>} a
 * @param {!Array<T>} b
 * @template T
 * @return {boolean} Whether two arrays are equal.
 */
function assertArrayEqual(a, b) {
  // Guard clause
  if (a.length != b.length) {
    return false;
  }
  for (var i = a.length; i >= 0; i--) {
    if (a[i] !== b[i]) {
      return false
    }
  }
  return true;
};

Test Suite

I think it would be good to show some TDD skills but I have avoided to use some external library instead don't know if its a good idea from interviewers' point of view.

console.log(assertArrayEqual(flatten([]), flatten([])));
console.log(assertArrayEqual(flatten([1]), flatten([1])));
console.log(assertArrayEqual(flatten([1, 2]), flatten([1, 2])));
console.log(assertArrayEqual(flatten([1, 2, 3]), flatten([1, 2, 3])));
console.log(assertArrayEqual(flatten([1, 2, [3]]), flatten([1, 2, 3])));
console.log(assertArrayEqual(flatten([1, 2, [3], [4, [5]]]), flatten([1, 2, 3, 4, 5])));
console.log(assertArrayEqual(flatten([[1, 2, [3]], 4]), flatten([1, 2, 3, 4])));

Questions

  1. I haven't really handled an error condition here, should I check if the passed input is an array type or not?

  2. Seeing the simplicity of the question I have avoided the added complexity of the OOP and followed a functional approach instead.

  3. I think I have covered the documentation well but any suggestions are welcomes(I think it can be modified if I decide to throw an exception).

  4. It seemed overkill to use module pattern or some other fancy stuff.

  5. I am using closure compiler to catch bugs earlier during development.

  6. Any other suggestions are welcomed here :)

\$\endgroup\$
4
  • 1
    \$\begingroup\$ The function itself looks good, one hint, Array.isArray is not supported in every browser developer.mozilla.org/de/docs/Web/JavaScript/Reference/… some other array functions too.. so you should use a polyfill if it should be compatible to across older browsers, or use something like lodash ;) \$\endgroup\$
    – webdeb
    Commented Jun 26, 2016 at 16:31
  • \$\begingroup\$ @webdeb All modern browsers support ES5. Array.isArray and array.reduce are both supported. \$\endgroup\$
    – Joseph
    Commented Jun 27, 2016 at 23:22
  • \$\begingroup\$ of course, you are right, if you are targeting only modern browsers it should be fine \$\endgroup\$
    – webdeb
    Commented Jun 28, 2016 at 0:49
  • 2
    \$\begingroup\$ Perhaps it's useful to take cycles into account: var a = []; a.push(a); flatten(a);, or at least to document that they're not supported? \$\endgroup\$ Commented Jun 29, 2016 at 8:53

3 Answers 3

7
+50
\$\begingroup\$
  1. Depends. If this is internal code, which is covered by your compiler, then there's no sense guarding. Your compiler already know who calls it and what's being passed in. However, if it's the public-facing code, I would guard against unexpected input.

  2. Totally acceptable. Besides, what's there to OOP when you're just manipulating array structure. Also, for fun, technically you're doing OOP. Arrays are objects in JS. In fact, everything aside from primitives stem from Object.

  3. No comment.

  4. If you plan to make more of these functions, consider namespacing them. Making them a property of one global object would suffice.

  5. If you are adventurous, you can also try out TypeScript. The only difference is that type hints are now in the language rather than just annotations.

  6. See following.


function flatten(elements) {
  return elements.reduce((result, current) => {
    return result.concat(Array.isArray(current) ? flatten(current) : current);
  }, []);
};
\$\endgroup\$
5
\$\begingroup\$
for (var i = a.length; i >= 0; i--) {
    if (a[i] !== b[i]) {
      return false
    }
  }

There is a logic error here. Remember that the i-- is evaluated at the end of the loop. So on first pass i===a.length which means a[i] and b[i] are undefined. Change i = a.length to i = a.length-1

This may not matter to your requirements since you use assertArrayEqual only with flattened arrays above, but it will return a false negative if you use it with arrays that contain non immutable members. Eg:

var a = [1,2,[3]];
var b = [1,2,[3]];
assertArrayEqual(a,b); // FALSE
\$\endgroup\$
5
\$\begingroup\$
function flatten(elements) {
  return elements.reduce(function(result, current) {
    if (Array.isArray(current)) {
      return result.concat(flatten(current));
    }
    return result.concat(current);
  }, []);
};

This is correct and simple, but it generates a tonne of garbage to be collected. Have you considered using a single accumulator?

function flatten(elements) {
  var accum = [],
      aggregate = function(element) {
        if (Array.isArray(element)) {
          element.forEach(aggregate);
        }
        else {
          accum.push(element);
        }
      };
  aggregate(elements);
  return accum;
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.