2

I would like to make an N-dimensional array flatten into a single array. The array will only have numbers, but every element need not necessarily be an array itself. Sample data: (md=multi-dimensional) fiddle

 var mdArray = [1,2,[3, 4], [[[5, 6, 7]]]];


 Array.prototype.flattenAll = function () {
         var self = this,
         //finds elements that are not arrays
             numbers = $.grep(self, function (n, i) {
                 return !$.isArray(n);
             }),
         //gets md arrays
             nestedArrays = $.grep(self, function (n, i) {
                 return $.isArray(n);
             }),
             returnArray = [];
         nestedArrays = $.map(nestedArrays, function (a, ix) {
             return a.flatten();
         });
         return returnArray.concat(numbers).concat(nestedArrays);
     };
     Array.prototype.flatten = function () {
             return this.reduce(function (a, b) {
                 return a.concat(b);
             });
     }

The flatten function works for a simple array of arrays, but not for arrays with a rank > 1. This seems like an easy fix with recursion, but I'm not seeing it. What am I missing here to get flatten to run until there are no more arrays to flatten?

6
  • 2
    if you always have numbers: String([1,2,[3, 4], [[[5, 6, 7]]]]).split(/,+/).map(Number) Commented Feb 25, 2014 at 19:10
  • @dandavis that's slick, can you explain how it works? Commented Feb 25, 2014 at 19:12
  • 1
    numbers can losslessly convert to strings, array can convert to CSV strings, and by splitting on commas and empty slots we are left with a non-sparse array of string numbers we convert back to real numbers using map(Number). It's a lot more to explain than code, which is why i love such simple JS goodness. Commented Feb 25, 2014 at 19:15
  • @dandavis I'm wrestled with this problem for a day now and it didn't even occur to me to try what you did :) Commented Feb 25, 2014 at 19:17
  • @wootscootinboogie it "prints" the array into the String representation (flatten by default), splits it near all the commas and iterates over each splitted chunk (a single number) adding it to the resulting array. Commented Feb 25, 2014 at 19:18

7 Answers 7

2

A very simple loop can take care of this:

Array.prototype.flattenAll = function () {
    var flattened = this.slice();
    for (var i=0; i<flattened.length; )
        if ($.isArray(flattened[i]))
            flattened.splice.apply(flattened, [i, 1].concat(flattened[i]));
        else
            i++;
    return flattened;
}
Sign up to request clarification or add additional context in comments.

4 Comments

could you explain what's happening inside the if statement?
The array is replaced by its elements. Check out the docs for the splice Array method. Unfortunately, it doesn't take an array of replacements (as in flattened.splice(i, 1, flattened[i])), so I had to use apply which makes it look a bit ugly.
This is overcomplicated, and it relies on jQuery, which is unnecessary.
@EthanBrown: I'd have used Array.isArray, but since the question was tagged jquery I chose the cross-browser variant.
2

You could do it in a single statement with Array.prototype.reduce:

Array.prototype.flatten = function (){
    return this.reduce(function(c,x){
        return Array.isArray(x) ? c.concat(x.flatten()) : c.concat(x);
    }, []);
}

As an aside, if you're going to modify core object prototypes (like Array.prototype), you should at least be using Object.defineProperty so your function isn't enumerable:

Object.defineProperty(Array.prototype, 'flatten', {
    value: function (){
        return this.reduce(function(c,x){
            return Array.isArray(x) ? c.concat(x.flatten()) : c.concat(x);
        }, []);
    },
    enumerable: false
});

If you want to see why that's a bad idea not to use Object.defineProperty, try the following:

Array.prototype.foo = function(){};
console.log([1,2,3]);

What you will see in Chrome is this:

[1, 2, 3, foo: function]

Which is probably not what anyone wants to see.... Using Object.defineProperty, and setting enumerable to false will prevent this from happening.

Other browser's console logs aren't quite so friendly. But if you want an iron clad example that will work in any browser, do this:

Array.prototype.foo = function(){};
for(var x in [1,2,3]) console.log(x):

6 Comments

spot on, on that remark, it has been annoying seeming all these enumerable properties in the console.
What browser logs prototype properties in the array representation of the object?
Hm. Looks like Firefox doesn't, but Chrome and IE do.
IMO, this is naughty of Firefox. What's logged to the console should reflect all of the objectc's properties, including enuemrable ones in the prototype chain. Because that's what you'll get if you do a for/in loop.
@EthanBrown: But you don't do for in loops on array-like objects… :-) Opera Dragonfly does show the properties only on the expanded the prototype as well.
|
2

You can do it with nice recursion, something like that:

function flatten(array, i) {
  i = ~~i;

  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

1
var mdArray = [1,2,[3, 4], [[[5, 6, 7]]]];
function flatten(mdArray){
    var data = [];
    $.each(mdArray,function(index,item){
        if(typeof(item) != 'object'){
            data.push(item);
        }else{
            data = data.concat(flatten(item));
        }
    });
    return data;
}
var result = flatten(mdArray);
console.log(result);

Comments

0
function flattenArrayOfArrays(a, r){
    if(!r){ r = []}
    for(var i=0; i<a.length; i++){
        if(a[i].constructor == Array){
            flattenArrayOfArrays(a[i], r);
        }else{
            r.push(a[i]);
        }
    }
    return r;
}

4 Comments

not to pick nits, but this won't work on all arrays, like those from another frame. i would duck-type: if(a[i].length) instead of sniffing constructors... if you need to guard against strings, you can ask if(a[i].join).
True, I was just using the example of the array given
@dandavis: Why would the constructor not be accessible when the array was from another frame? Would that be because that other parent window would have its “own” kind of instance of it or something?
Array belongs to window, so a frame has a different Array and thus != top.Array...
0

You could use lodash's flatten operator;

var mdArray = [1,2,[3, 4], [[[5, 6, 7]]]];

console.log(_.flatten(mdArray)); // Outputs [1, 2, 3, 4, 5, 6, 7] 

Comments

0

This is a Vanilla JavaScript solution to this problem

var _items = {'keyOne': 'valueOne', 'keyTwo': 'valueTwo', 'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]};

// another example
// _items = ['valueOne', 'valueTwo', {'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]}];

// another example
/*_items = {"data": [{
    "rating": "0",
    "title": "The Killing Kind",
    "author": "John Connolly",
    "type": "Book",
    "asin": "0340771224",
    "tags": "",
    "review": "i still haven't had time to read this one..."
}, {
    "rating": "0",
    "title": "The Third Secret",
    "author": "Steve Berry",
    "type": "Book",
    "asin": "0340899263",
    "tags": "",
    "review": "need to find time to read this book"
}]};*/

function flatten() {
    var results = [],
        arrayFlatten;

    arrayFlatten = function arrayFlattenClosure(items) {
        var key;
        
        for (key in items) {
            if ('object' === typeof items[key]) {
                arrayFlatten(items[key]);
            } else {
                results.push(items[key]);
            }
        }
    };

    arrayFlatten(_items);
    
    return results;
}

console.log(flatten());

Comments

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.