views:

104

answers:

6

I'm using PHP to fetch "tasks" from my database and encoding it as JSON. When I transfer the data over to javascript, I end up with something like this:

Array {
   [0] => Task {
      id: 2,
      name: 'Random Task',
      completed: 0
   }
   [1] => Task {
      id: 8,
      name: 'Another task',
      completed: 1
   }
}

etc.

I guess my real question is, what's the most efficient way to find the task by its id? Iterating through the array and checking each object seems like it might not be the most efficient? Is there any other way to do this?

+2  A: 

Usually the most efficient way to iterate over an array collection in Javascript is to stick to the native for loop. The reason I say "usually" is that the implementation comes down to each unique browser's implementation of javascript so there is no absolute definitive answer.

There's a nice post at http://solutoire.com/2007/02/02/efficient-looping-in-javascript/ which covers the performance of each of the main iteration methods and empirically comes to the same conclusion.

Brian Scott
Thanks for the info, I guess I'll be writing some function to do what I need using a for loop
Rob
A: 

If id is unique (and mostly continuous) you can do a one time rearrange of the array so that array index reflects the id. If they're not unique, you can sort them and do a binary search.

But this would be useful only if you access the items by id from the array frequently, otherwise the overhead of sorting won't be worth it.

Amarghosh
The thing is, id may not be continuous at all. There will be thousands and thousands of tasks, so the tasks for a given user may start at 1 or 2000 including anything in between. I was messing with the idea of using id as the index, but then I debugged the array and found something like:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,40,,,,,,,,,,,,etc
Rob
+5  A: 

The thing about Javascript objects is that they are essential maps. You can access properties through using both dot notation ("object.property") and also index notation ("object["property"]). You can also enumerate through its properties, either using a for (i...) or for (in...)

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

for (var prop in arrayObj) { ... }

What I have been doing recently is building some Linq-esque extensions to the array object:

Array.prototype.Where = function(predicate) {
    Throw.IfArgumentNull(predicate, "predicate");
    Throw.IfNotAFunction(predicate, "predicate");

    var results = new Array();
    for (var i = 0; i < this.length; i++) {
        var item = this[i];
        if (predicate(item))
            results.push(item);
    }

    return results;
};

Ignoring my custom Throw type, it basically allows you do to something like:

var item = arrayObj.Where(function(i) { return (i.id == 8); }).FirstOrDefault();

I'll publish it all at some point if you are interested?

Matthew Abbott
I love that idea. I was thinking about writing something similar..along the lines of:Array.findObjectByKey('key', 'val');I guess it only makes sense that I'd have to iterate the whole array if I want to find what I need.
Rob
Well give me about a couple of hours and I'll publish it on my blog, that way you can use all the methods I've built?
Matthew Abbott
Sounds good, thanks.
Rob
Unless you have a small array, you should define a variable for `arrayObj.length`. You can do smth like this: `for(var i=0, len=arrayObj.length; i<len;i++)` Details here: http://dev.opera.com/articles/view/javascript-best-practices/#loops
Ionut Staicu
Wow nice work.......... it looks like .NET linq .... :)
Anil Namde
@Matthew Abbott: Nice solution but how does this perform as per the original eqnuiry regarding efficiency?
Brian Scott
That's quite a good pint Ionut, will revise in future. Rob, I've blogged it here (not an extensive blog post, but enough):http://www.fidelitydesign.net/?p=149.@Brian Scott, to be honest mate I haven't had a chance to look at performance yet, it's nowhere near production code, just something quickly thrown together. I appreciate feedback though :)
Matthew Abbott
I really like this idea, it proved to be very useful. Thanks again
Rob
A: 

Is your array large? If not, you probably won't win many microseconds on optimizing it.

If it is large, you should really return a dictionary instead (As Matthew Flaschen commented), which uses the task's ID as key. In this way you'll get constant time lookup (atleast if the javascript implementation is optimal).

Just use a ordinary PHP associative array, and run it through json_encode or whatever you're using.

//Assume you have your original Array named $tasks:
$dictionary = Array();
foreach($tasks as $task)
    $dictionary[$task->getID()] = $task;
Yngve Sneen Lindal
+1  A: 

If you don't need to maintain order, then the best way is to a regular object, and index by task id. That gives you O(1) access.

var tasks = {
   '2': {
      id: 2,
      name: 'Random Task',
      completed: 0
   },
   ...
}

If you also need ordering maintained, then write an OrderedMap "class" that maintains the order by creating an array of task ids, but the actual tasks will still be stored in an object indexed by task id. So essentially you would have:

// internal API (to help maintain order)
taskIDs = [a, b, c, ..];
// internal API (for actual storage)
tasks = {
    a: { .. },
    b: { .. },
};

// external API for iterating objects in order
forEach(fn);
// external API for accessing task by ID
get(id);

The outside world can be ignorant of how you maintain order as long as you provide a nice encapsulated way of iterating these in order, and accessing them by task id.

If you need reference for implementing such a class, see the source for LinkedMap from Google Closure Library.

Anurag
+1  A: 

Just a little more food for thought, this is what I ended up with:

this.find = function (test) {
    var results = [];
    for (var i = 0,l = this.tasks.length; i < l; i++) {
        var t = this.tasks[i];
        if (eval(test)) {
            results.push(this.tasks[i]);
        }
    }
    return results;
}

this allows me to do a simple tasks.find('t.id == 2') or tasks.find('t.completed == 1');

Rob
+1 nice solution.. one change I would make to this this wrap the eval test inside a with statement, so the calling client does not have to worry about what you've called that temporary variable inside. They can simply do `tasks.find("id == 2")`.. mocked up an example - http://jsfiddle.net/GQ5RQ/
Anurag