3

I've read a lot of articles about objects and arrays lately but they had contrary info / facts for some reason. I need your help to learn once and for all: when it's best to use array and when object?

Obvious reason is to use array when you need specific order. What else? What about this example?


  • There's a lot of pushing
  • There's a lot of checking if array contains something
  • There's a lot of removing

Note that this example has much smaller numbers that I need (which is in thousands).


Code I wrote for this question, it has few things I would never do but for the sake of this question:

var x = [];
var y = [];
var z = [];

var clickedX = [];
var clickedY = [];
var clickedZ = [];

var count = 100;

for ( var a = 0; a < count; a++ ) {

    //For the sake of this example: random int 1, 2 or 3
    var type = Math.floor(Math.random() * 3) + 1;

    createDiv( type );
}


function createDiv( thisType ) {

    var self = this;
    var div = self.div;

    var type = thisType;

    if( !div ) {

        div = this.div = document.createElement( 'div' );
        div.className = 'marker';

        //Push to "right" array
        if( type == '1' ) {

            x.push( self );
        }
        else if ( type == '2' ) {

            y.push( self );
        }
        else {

            z.push( self );
        }

        //Attach click event
        div.onclick = function() {

            // X
            var indexX = x.indexOf( self );

            if( indexX > -1 ) { 

                //Push to new array
                clickedX.push( self );

                //Remove from old array
                x.splice( indexX, 1 );
            }

            // Y
            var indexY = y.indexOf( self );

            if( indexY > -1 ) { 

                //Push to new array
                clickedY.push( self );

                //Remove from old array
                y.splice( indexY, 1 );
            }

            // Z
            var indexZ = z.indexOf( self );

            if( indexZ > -1 ) { 

                //Push to new array
                clickedZ.push( self );

                //Remove from old array
                z.splice( indexZ, 1 );
            }
        }; // onclick
    } // if( !div )
} // createDiv()

Data example what Im currently dealing with:

// Data Im dealing with

objects = { 

    { objects1: [
                    0: { object : [4-5 assigned values (int, string, array)] }, 
                    1: { object : [4-5 assigned values (int, string, array)] },
                    //etc
                ] 
    }, 
    { objects2: [
                    0: {object}, 
                    1: {object},
                    //etc
                ] 
    } 
}

// 1. I need to iterate through every "object" in both "objects1" and "objects2"
// 2. I need to iterate through "array" in every "object" in "objects1"


for( /* "object" count in "objects1" */ ) {

    //Do something for each "object" in "objects1"

    for( /* items in "array" count */ ) {

        //Do something for each item in "array"
    }
}

// AND

for( /* "object" count in "objects2" */ ) {

    //Do something for each "object" in "objects2"
}
Solo
  • 6,687
  • 7
  • 35
  • 67
  • It's a totally different things. It's simple, when you have a **list** of things, you need an `array`. You can `sort`, `filter`, `reverse` and more. It's like the difference between _bottlle of milk_ and _shop list_. (Unless I completely didn't understand the question). – Mosh Feu Jan 20 '16 at 10:53
  • 1
    `this` in the context of the `createDiv` function is not what you think it is. do a console log to check it and find out. Use an object if you know the key to retrieve the value, or if you have a collection of things and you need to iterate over them use an array. For the above an array is the most appropriate. – synthet1c Jan 20 '16 at 10:54
  • @MoshFeu Im more interested about adding new item / checking _if contains something_ / removing an item actions. I've read that it's faster to use object and name rather than using `IndexOf() and splice()`, is that true? – Solo Jan 20 '16 at 10:56
  • I don't know but it's make sense.. – Mosh Feu Jan 20 '16 at 11:00
  • @synthet1c Thanks for pointing that out, I wrote it as an example and had no itention to use that code. How about comparing an object and array both containing 10 000 elements and I know the key Im searching for? Which is more appropriate? – Solo Jan 20 '16 at 11:00
  • what do you need to do with the data / objects? do you need to iterate them and treat them as a collection of things, or just have a nice organised place to store things? – synthet1c Jan 20 '16 at 11:02
  • @synthet1c I need them as a collection _(for example I need to show / hide all items in that array / object)_ but I also need to find a single item from that collection fast and I know the key. This is rather complicated situation.. I have never dealt with big numbers _(50k - 100k)_ when it comes to arrays and objects and Im afraid that `IndexOf() and splice()` are slow with that kind of numbers.. Please tell me that Im wrong. – Solo Jan 20 '16 at 11:09
  • Managing the DOM is always a problem, but if you can, you should put these elements into containers, so that you DO NOT iterate over every array item, but instead just hide one container. – user1234141515134321321513 Jan 21 '16 at 07:56

3 Answers3

4

Arrays

Basically, you need to use an array, when you have a list of consequent data, and you are going to work with it as a collection.

It easily adds, iterates, gets count and checks for existence.
It is difficult to remove items in an array.

Also, it stores order.
For example, if you have 10 integers, and you need to find the index of the minimum one, then it is logical to use an array.

Objects

Use objects, when your items have keys (especially, non-integer) and you are going to work with them one by one.

It is convenient to add, remove, check for existence properties. However, it is less convenient to iterate through object properties, and absolutely inproper to get its count.

It is also impossible to store items' order.

Time complexity

Answering your question about pushing, checking for existence and removing:

  • Both property setting and array pushing takes O(1) time, but I strongly believe that the first one is a bit slower
  • Checking for existence takes the same O(1) time
  • Removing an element is what array is not designed for - it needs to shift all items and takes O(n) time, while you can remove a property for O(1)

Bad usage example

There are some really bad usages. If you have the following, then you use array or object wrong.

  1. Assign a random value to an array:

    var a = [];
    a[1000] = 1;
    

    It will change the length property of an array to 1001 and if you try to output this array, you will get the following:

    [undefined,undefined,undefined,undefined... 1]
    

    which is absolutely useless, if it is not done on purpose.

  2. Assign consequent values to an object

     var a = {};
    
     for (var i = 0; i < 10; i++)
     {
         a[i] = 1;
     }
    

Your code

Talking about your code, there are many and many ways to do this properly - just choose one. I would do it in the following way:

var items = [];

for (var i = 0; i < count; i++)
{
    var o = {
        type: Math.floor(Math.random() * 3) + 1,
        isClicked: false
    };

    var div = document.createElement('div');
    div.className = 'marker';

    div.onclick = function() {
        o.isClicked = true;
    };

    o.div = div;
    items.push(o);
}

function GetDivs(type, isClicked)
{
    var result = items;
    if (type) 
        result = result.filter(function(x) { return x.type === type; });
    if (isClicked !== undefined) 
        result = result.filter(function(x) { return x.isClicked === isClicked; });
    return result;
}

Then, you will be able to get any items you want:

var all = GetItems(0); // or even GetDivs()
var allClicked = GetItems(0, true);
var allNotClicked = GetItems(0, false);

var divsTypeA = GetItems(1);
var clickedDivsTypeB = GetItems(2, true);
var notClickedDivsTypeC = GetItems(3, false);

With this usage, you don't need to remove any items at all - you just mark them as clicked or not, which takes O(1) time.

jQuery

If you use jQuery and data HTML attributes, then you won't need to use arrays or objects at all:

for (var i = 0; i < count; i++)
{
    $('<div/>')
      .addClass('marker')
      .attr('data-type', Math.floor(Math.random() * 3) + 1)
      .appendTo('body')
      .click(function() {
          $(this).addClass('clicked');
      });
}

Now, you can use the selector to find any items:

var all = $('.marker');
var allClicked = $('.marker.clicked');
var allNotClicked = $('.marker:not(.clicked)');

var divsTypeA = $('.marker[data-type='1']');
var clickedDivsTypeB = $('.marker[data-type='2'].clicked');
var notClickedDivsTypeC = $('.marker[data-type='1']:not(.clicked)');

However, the last approach can produce lags if you really have thousands of records. At the same time, is it a good idea to have 1000 dynamic divs on your page, at all? :)

Yeldar Kurmangaliyev
  • 33,467
  • 12
  • 59
  • 101
  • Nice answer, the `[null, ...]` part is incorrect: it is rather `[undefined, ...]` (properties are actually not set). The part about the fact it is useless to use sparse arrays is not always true, even though in most case it is useless, and can be harmful if you assume array higher order functions (map, reduce, etc.) will loop through `undefined` keys (and they won't). – axelduch Jan 20 '16 at 11:28
  • This is a great answer, I actually learned something from it. `Div`s are markers on map, I add them in smaller batches _(few hundred at a time)_ and Im thinking of using `visibility` to show/hide them on different zoom levels and when out of viewport which should be better compared to constantly adding / destroying them. I will not consider jQuery because I might get rid of jQuery entirely if Im good enough in plain JS. I will need several arrays / objects to keep track of markers on map, clicked markers and there's even more actions.. I'll just add more args to `getDivs` in that case, right? – Solo Jan 20 '16 at 12:01
1

When all else fails run a test, Again it depends on what you intend to use the data structure for, everything is a trade off, performance || efficiency.

Be prepared to wait a few seconds for the tests to finish.

function gimmeAHugeArray( length ){
  var arr = [];
  for( var ii = 0; ii < length; ii++ ){
    arr.push( (ii * 100000 ).toString(16) );
  }
  return arr;
}

function gimmeAHugeObject( length ){
  var obj = {};
  for( var ii = 0; ii < length; ii++ ){
    obj[ (ii * 100000 ).toString(16) ] = ii;
  }
  return obj;
}

var hugeArray = gimmeAHugeArray( 100000 );
var hugeObject = gimmeAHugeObject( 100000 );
var key = (8000 * 100000).toString(16);

console.perf(function arrayGetWithIndexOf(){
  var val = hugeArray.indexOf( key );
});

console.perf(function objectGetWithKey(){
  var val = hugeObject[ key ];
});

console.perf(function arrayIterateAllProperties(){
  var val = null;
  for( var ii = 0, ll = hugeArray.length; ii < ll; ii++ ){
    val = hugeArray[ii];
  }
}, 50);

console.perf(function objectIterateAllProperties(){
  var val = null,
      key;
  for( key in hugeObject ){
    val = hugeObject[ key ];
  }
}, 50);
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>
synthet1c
  • 6,152
  • 2
  • 24
  • 39
  • Dude.. This is very illuminating. Basically bottom line is that if I need to iterate all, array is the way to go and if I search for single item while I know the key, object is the way to go.. Now, the million-dollar question is: **What to do if I need to do both with same data?** – Solo Jan 20 '16 at 11:50
  • If you have to iterate use an array. The overhead is minor compared to iterating through the object. It also depends what you are storing. if it's an Object or Array you are storing in the data structure, you can store a reference to the same object in both the array and the object, but you would need to manage both when modifying either structure. If you are storing a primitive value then you couldn't do it as primitives are passed by value not reference. – synthet1c Jan 20 '16 at 11:58
1

I'm going to answer your question from the comment above, from Solo.

The question is What to do if I need to do both with same data?

I have found that the best approach ( at least for me ) was to create an array, that may have empty nodes, but keep another array that will keep track of the ids that are populated.

It looks something like this:

var MyArray = function()
{
    this.storage = [];
    this.ids = [];
};

MyArray.prototype.set = function(id, value)
{
    this.storage[id] = value;
    this.ids[this.ids.length] = id;
};

MyArray.prototype.get = function(id)
{
    return this.storage[id];
};

MyArray.prototype.delete = function(id)
{
    delete this.storage[id];
};

This solution works quite well with both approaches, because you can delete anything at O(1), because everyone has a given ID, and you iterate it easily, because you have all of the IDs the values are stored at.

I have also created a really small class for my own usage, and it performs VERY well. Here's the link https://github.com/ImJustAskingDude/JavascriptFastArray . Here's a question I asked about these things: Javascript Array Performance .

Please read what I write there, this solution applies to a pretty particular situation, I do not know exactly if it applies to you.

That is why I asked you to read what is in those links I provided, I have written like 10 pages worth of material on this, even though some of it is pure garbage talk about nothing. Anyway, I'll write a couple of examples here as well, then.

Example:

Let us say you had data from the database, the data from the database is pretty much required to have unique integer IDs, but each user may have them spread over a wide range, they are MOST LIKELY not contiguous.

So you get the data from the database into objects, something like this:

var DBData /* well, probably something more descriptive than this stupid name */ = function(id, field1, field2, field3)
{
    this.id = id;
    this.field1 = field1;
    this.field2 = field2;
    this.fiedl3 = field3;
};

Given this class, you write all of the data from the database into instances of it, and store it in MyArray.

var data = new MyArray();

// YOU CAN USE THIS, OR JUST READ FROM DOM
$.get(/* get data from DB into MyArray instance */, function(data) { /* parse it somehow */ var dbdata = new DBData(/* fields that you parsed including ID */);  data.set(ID, dbdata)});

and when you want to iterate over all of the data, you can do it like this:

Just create a function to create a new array, that has all of the data in a nice contiguous block, it would go something like this:

    function hydrate(myarray, ids)
    {
        var result = [];

        var length = ids.length;

        if(!length)
        {
            return null;
        }

        var storage = myarray.storage;

        for(var i = 0; i < length; i++)
        {
            result[result.length] = storage[ids[i]];
        }

        return result;
    };

I use this method, because you can use it to select any configuration of objects into a contiguous array very easily.

One solution to handle arrays of arrays, would be to just modify MyArray to deal with that exclusively, or make a version of it, that handles it.

A different solution would be to rethink your approach, and maybe add all of the data in a single MyArray, but put all of the

objects1: [
                    0: { object : [4-5 assigned values (int, string, array)] }, 
                    1: { object : [4-5 assigned values (int, string, array)] },
                    //etc
                ] 
    }, 
    { objects2: [
                    0: {object}, 
                    1: {object},
                    //etc
                ] 
    } 

into nice objects.

And seriously, read what I wrote in those links, there's my reasoning for everything there.

Community
  • 1
  • 1
  • This seems like a great solution, it will probably take day or two to figure out how to use it in my situations (**I updated my question**). So, you're making an object with two arrays, first holds ids and second one holds values, this basically means that you benefit from speed of single item search if you know the key in object but how about iterating and doing something for all items? **Could you add few situation examples?** Also, how to apply this situation to several arrays? Just copy-paste or could it be even more generalized to be appliable for several arrays? Again, great stuff! – Solo Jan 20 '16 at 13:17
  • Thanks for the update, I browsed your links, I will definately make time to read them. Just a quick question *(I don't have the time to read your links today)*: your solution doesn't **remove** anything from storage array? It just scales all the time? Will it work well with few hundred thousand items? Did your tests reached to *a point* where speed was decreasing _(by noticeable amount)_? – Solo Jan 20 '16 at 16:52
  • In the thing I wrote right here, the performance will drop like a stone after a 1000 elements, that's just how all major ( Firefox and Chrome ) handle JavaScript arrays, but in that github link, there is an implementation that I explicitly made to counter that, if you make array of arrays of arrays, you get room for a 1 000 000 000 elements, if you exceed the 1000 element in any individual array, the performance will drop like a stone again, but if all of them remain under a 1000, they are going to be lightning fast. – user1234141515134321321513 Jan 21 '16 at 07:48
  • I did not really notice the performance dropping, this solution was always faster than either plain object or plain array, unless you were targeting a single browser. I do not remember the specifics, because there are far too many configurations, but I think Firefox handled arrays WAY better than Chrome, and Chrome handled objects WAY better than Firefox, the holes do matter as well. There are some jsperfs in that SO link, I think, but jsperf lies! Anyway, your question: It does remove them, or at least you can remove elements, in does not matter, you can delete any element in storage. – user1234141515134321321513 Jan 21 '16 at 07:54