2

I can not figure out why my code does not flatten out the nested arrays as indicated. I'd greatly appreciate some help here. I used a recursion to get to the actual value of the nested array. I tried to debug my code, and it seems to replace my results array every time the recursion takes place.

//Helper methods

function toType(obj){
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}

function each(collection, callback){
    if (Array.isArray(collection)){
        for (var i = 0; i < collection.length; i++){
            callback(collection[i], i, collection)
        }
    } else {
        for (var i in collection){
            callback(collection[i], i, collection)
        }
    }
}

//Flatten function

function flatten(array, isShallow=false, callback){
    var results = [];
    each(array, function(item){
        if (!!isShallow && toType(item) === 'array'){
            each (item, function(value){
                results.push(value);
            })
        } else if (toType(item) !== 'array'){
            results.push(item);
        } else {
            return flatten(item)
        }
    })
    return results;
}

flatten([1, [2], [3, [[4]]]]);

// ---> [1]
2
  • Golf answer: function flatten(a){return a.reduce(function(a,b){return a.concat(b.map?flatten(b):b)},[])} Commented Jun 17, 2014 at 21:20
  • 1
    In what environment you are running this code? It’s required to ECMA6 support as you are using developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Commented Jun 17, 2014 at 21:23

5 Answers 5

2

Your problem appears to be with this line:

return flatten(item)

Returning here is a problem because the loop will end and the current entries in results will be ignored, as well as the remaining items. The line should be changed to instead append the results of

flatten(item) 

to results array via push.

I recommend using a library for this sort of thing. http://underscorejs.org/#flatten is a great one!

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your reply. I would use a library for this sort of thing, but since I am pretty new at Javascript, my friend recommended me to write my own functions that would work the same as if I were using Underscore or Lo-Dash. removing 'return' provides me with the same output.
did you try replacing the return flatten(item) with something like this: each(flatten(item), function(x) {results.push(x);});
I just tried, and that does provide me with the answer I am looking for. Is there a reference you can suggest so that I could figure out why I am able to reference the 'var results' as opposed to my original code? Is it mainly a problem regarding my understanding of variable scope? Edit: Ah, I see how this change in code works. Thanks again!
JavaScript: The Good Parts is a very good book and is where I learned about JavaScript's scoping. It seems right up your alley (given your earlier comments about learning JavaScript). I recommend opening a browser console or node.js command line interface and trying things as you read through the book. Here's a link to a page that seems to explain each of the concepts you'll want to understand: ryanmorr.com/understanding-scope-and-context-in-javascript
1

Please see the refactored code below.

The major change is that instead of creating new copies of results, we are passing it to subsequent calls to flatten as a reference.

Please see the added comments

//Helper methods

function toType(obj){
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}

function each(collection, callback){
    if (Array.isArray(collection)){
        for (var i = 0; i < collection.length; i++){
            callback(collection[i], i, collection)
        }
    } else if(typeof collection === 'object'){ 
        //type number was failing here
        for (var i in collection){
            callback(collection[i], i, collection)
        }
    }
    else {
        //default for primitive types
        callback(collection, 0, collection);
    }
}
//Flatten function

//Removed isShallow, how do we know if its shallow or not?
//Added results as arg, so we only manipulate the reference to results
//And to not create multiple scopes of var results;
function flatten(array, results, callback){
    results = results || [];
    each(array, function(item){

        //removed 3rd if clause not needed. 
        //Only need to know if item is an object or array
        if (toType(item) === 'array' || toType(item) === 'object'){
            each (item, function(value){
                flatten(value,results);
            })
        } else {
            results.push(item);
        }
    })

    return results;
}


var array1 = [1,[2,[3,4]]];
var array2 = [5,[6,[7,[8, {a:9,b:[10,11,12]}]]]];
var obj = {a:array1, b:array2};


console.log(flatten(array1)); // [ 1, 2, 3, 4 ]
console.log(flatten(array2)); // [ 5, 6, 7, 8, 9, 10, 11, 12 ]
console.log(flatten(obj)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]

3 Comments

Thank you for your answer. This was very helpful for me. Looks like I need to learn to get a better understanding of variable scope.
@svntwosix, don't forget to pick an answer if any of them resolved your issue. Also, is there any reason you added a callback to flatten()?
@Gabbs, Thanks for the tip. What I wanted to replicate was Lo-Dash's use of the function flatten. (lodash.com/docs#flatten). Callback will be used to flatten properties in an object.
0

You can do something like that:

function flatten(array, i) {
  i = i || 0;

  if(i >= array.length)
    return array;

  if(Array.isArray(array[i])) {
    return flatten(array.slice(0,i)
      .concat(array[i], array.slice(i+1)), i);
  }

  return flatten(array, i+1);
}

Example:

var weirdArray = [[],1,2,3,[4,5,6,[7,8,9,[10,11,[12,[[[[[13],[[[[14]]]]]]]]]]]]]
flatten(weirdArray);
//returns ==> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

Comments

0

The best way to flatten you array in javascript is to use splice function of Array. Please follow the following code. It works for nesting as well.

 function flattenArray(arr){

  for(var i=0;i<arr.length;i++){

    if(arr[i] instanceof Array){

      Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
    }

  }

  return arr;
}

Comments

-1

Use underscore.js's flatten function (http://underscorejs.org/#flatten). Underscore.js is a 3rd party library with 80 some-odd functions to make your life as a javascript programmer easier. Don't reinvent the wheel.

var _ = require('underscore');
_.flatten([1, [2], [3, [[4]]]]);
=> [1, 2, 3, 4];

3 Comments

Nonetheless, the question was to "figure out why [the] code does [not work]"
I am simply suggesting [that] the OP use a 3rd party library to do something in 2 lines. Not only does using underscore [in this case] make the code easier to read, it also would have saved the OP from working out all the nasty recursion that he[/she/it] is clearly having problems with.
Thanks for your reply. I would use a library for this sort of thing, but since I am pretty new at Javascript, my friend recommended me to write my own functions that would work the same as if I were using Underscore or Lo-Dash. Recursions are not my strong suit.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.