12

I am trying to find the index of an object within an array. I know there is a way to do this with underscore.js but I am trying to find an efficient way without underscore.js. Here is what I have :

var arrayOfObjs = [{
  "ob1": "test1"
}, {
  "ob2": "test1"
}, {
  "ob1": "test3"
}];

function FindIndex(key) {
  var rx = /\{.*?\}/;            // regex: finds string that starts with { and ends with }
  var arr = [];                  // creates new array
  var str = JSON.stringify(arrayOfObjs);          // turns array of objects into a string
  for (i = 0; i < arrayOfObjs.length; i++) {      // loops through array of objects
    arr.push(str.match(rx)[0]);                   // pushes matched string into new array
    str = str.replace(rx, '');                    // removes matched string from str
  }
  var Index = arr.indexOf(JSON.stringify(key));   // stringfy key and finds index of key in the new array
  alert(Index);
}

FindIndex({"ob2": "test1"});

JSFIDDLE

This works but I am afraid it isn't very efficient. Any alternatives?

Anthony
  • 1,439
  • 1
  • 19
  • 36
  • do you need it just once or more than once? – Nina Scholz Apr 29 '16 at 20:26
  • I need it more than once. This is just a small sample – Anthony Apr 29 '16 at 20:27
  • Consider a slightly different approach with [this lodash method](https://lodash.com/docs#findIndex) – Nick Zuber Apr 29 '16 at 20:28
  • i would take a hash table – Nina Scholz Apr 29 '16 at 20:28
  • Since you are passing in an object are you expecting to test against multiple keys? – Patrick Evans Apr 29 '16 at 20:30
  • There is no efficient way of checking wether or not two different objects contain the same keys and values. – adeneo Apr 29 '16 at 20:30
  • Yes. I am expecting to test against multiple keys – Anthony Apr 29 '16 at 20:31
  • @adeno So I should use a js plugin? – Anthony Apr 29 '16 at 20:32
  • 3
    You could, but it won't be any more efficient, it's still javascript – adeneo Apr 29 '16 at 20:33
  • @NinaScholz I may have to convert my array of objects to hash tables. Thanks for the advice – Anthony Apr 29 '16 at 20:35
  • 1
    If comparing the stringified objects is enough, you could at least drop all the regex stuff, and just iterate -> **https://jsfiddle.net/g30myfnh/1/** – adeneo Apr 29 '16 at 20:36
  • 1
    @Anthony That sounds like a better idea if you're able to do that. If you need to look up these objects and get their index (key) often, hash table would be a great solution. Hashtables are also surprisingly easy to write in JavaScript too, since everything in JavaScript is basically build off a hashtable already – Nick Zuber Apr 29 '16 at 20:37
  • 1
    @Anthony Also, as a side note, when it comes to speed efficiency **always** try to avoid `JSON.stringify()`; that method is *incredibly* slow – Nick Zuber Apr 29 '16 at 20:38
  • can you use `filter()`? (not supported in older browser). see: http://stackoverflow.com/questions/8670345/trouble-using-indexof-on-a-complex-array – stephen.vakil Apr 29 '16 at 20:38
  • By "efficient" do you mean in number of milliseconds to get a result? Amount of memory consumed? You're have received a lot of options (and will likely get more), but without objective data to compare them against, I don't see how to vote among them. – Heretic Monkey Apr 29 '16 at 20:49
  • Both time and memory. Time is a bit more important in this case. But whatever you deem as more efficient, I would give an upvote to. – Anthony Apr 29 '16 at 20:58

6 Answers6

7

Here's one way to do it, somewhat reliably and a little more efficiently, using some() and stopping as soon as the objects don't match etc.

var arrayOfObjs = [{
  "ob1": "test1"
}, {
  "ob2": "test1"
}, {
  "ob1": "test3"
}];

function FindIndex(key) {
    var index = -1;

    arrayOfObjs.some(function(item, i) {
     var result = Object.keys(key).some(function(oKey) {
            return (oKey in item && item[oKey] === key[oKey]);
        });
        if (result) index = i;
        return result;
    });
    
    return index;
}

var index = FindIndex({"ob2": "test1"});

document.body.innerHTML = "'{\"ob2\": \"test1\"}' is at index : " + index;
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • When you say somewhat reliably, do you mean it may not work in every case? – Anthony Apr 29 '16 at 20:55
  • 1
    @Anthony - It works in all cases where the values are primitives, and probably for other values as well, but probably not where the values are functions, prototypes or other objects etc. Comparing objects with any kind of data can be complicated, this is a quick fix for most "regular" objects. – adeneo Apr 29 '16 at 20:57
  • Just curious: Is there a reason you use `arrayOfObjs.some` and assign an index instead of just doing a `for` loop and returning the index immediately? – Mike Cluck Apr 29 '16 at 21:03
  • The first `some()` stops as soon as a matching object is found, the second `some()` stops as soon as a key or value doesn't match, so as to not waste time iterating on when an object doesn't match because it doesn't have the key, or the value isn't the same, or for the outer `some()` when a matching object is already found. – adeneo Apr 29 '16 at 21:05
  • @adeneo Nah, I got that. I was just saying that if you replaced the "outer" `some` with a `for` loop you would save yourself a function call. It would still exit the loop as soon as the first match was found. Either way, this is a good answer. – Mike Cluck Apr 29 '16 at 21:10
  • 1
    @MikeC - indeed it would, and the inner loop could use a `break` and a regular for loop, would probably be even more efficient in many browsers, as `some()` isn't very fast in some browsers – adeneo Apr 29 '16 at 21:12
6

A hash table with an example of access.

var arrayOfObjs = [{ "obj1": "test1" }, { "obj2": "test1" }, { "obj1": "test3" }],
    hash = {};

arrayOfObjs.forEach(function (a, i) {
    Object.keys(a).forEach(function (k) {
        hash[k] = hash[k] || {};
        hash[k][a[k]] = i;
    });
});

document.write('<pre>' + JSON.stringify(hash['obj2']['test1'], 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(hash, 0, 4) + '</pre>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 1
    How does this help them get the index of the matching object in `arrayOfObjs`? – Mike Cluck Apr 29 '16 at 20:48
  • @MikeC, now -- by now. – Nina Scholz Apr 29 '16 at 20:50
  • 1
    Note that you're relying on order in objects – adeneo Apr 29 '16 at 20:54
  • actually there is only one property. in RL, i would use a array with a fixed order for the properties. or a different approach. ;) – Nina Scholz Apr 29 '16 at 20:58
  • 1
    This would certainly be faster for lookups than anything else, and is by far the most efficient way, and I think I misread it at first, as there wouldn't be an issue with order in objects when you're storing the index from the outer iteration over the array. – adeneo Apr 29 '16 at 21:03
5

One way of doing this would be to use every to see if each key in the "filter" has a matching, correct value in an object. every ensures that the loop stops as soon as it finds a mismatched or missing value.

function log(msg) {
  document.querySelector('pre').innerHTML += msg + '\n';
}

var arr = [
  {
    a: 1
  },
  {
    b: 2
  },
  {
    c: 3,
    d: 4
  },
  {
    a: 1 // Will never reach this since it finds the first occurrence
  }
];

function getIndex(filter) {
  var keys = Object.keys(filter);
  for (var i = 0, len = arr.length; i < len; i++) {
    var obj = arr[i];
    var match = keys.every(function(key) {
      return filter[key] === obj[key];
    });
    if (match) {
      return i;
    }
  }
  
  return -1;
}

log(getIndex({ a: 1 }));
log(getIndex({ b: 2 }));
log(getIndex({ c: 3 }));
log(getIndex({ c: 3, d: 4 }));
log(getIndex({ e: 5 })); // Doesn't exist, won't find it
<pre></pre>
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
1

For an alternative to your customly built approach, lodash's findIndex method does exactly this for you:

var arrayOfObjs = [{
  "ob1": "test1"
}, {
  "ob2": "test1"
}, {
  "ob1": "test3"
}];

_.findIndex(arrayOfObjs, {"ob2": "test1"}); // => 1
Nick Zuber
  • 5,467
  • 3
  • 24
  • 48
1

Since testing equality on two different objects will always return false you could first test keys and then values ,

using reduce :

var arrayOfObjs = [{
  "ob1": "test1"
}, {
  "ob2": "test1" , k2:2
}, {
  "ob1": "test3"
}];

function getI( obj, arr){
 const checkK= Object.keys(obj);
 return arr.reduce((ac,x,i) => {
  if ( checkK.every(z =>  x[z] && obj[z] === x[z]) )
    ac.push(i);
  return ac;
  },[])
}

document.write( 'result is :'+ getI({ob2:'test1', k2:2},arrayOfObjs))
maioman
  • 18,154
  • 4
  • 36
  • 42
1

findIndex won't work in old browsers, but was designed for this specific purpose.

var arrayOfObjs = [{
  "ob1": "test1"
}, {
  "ob2": "test1"
}, {
  "ob1": "test3"
}];

function FindIndex(key) {
  return arrayOfObjs.findIndex(
    obj => Object.keys(key).every(name => key[name] === obj[name])
  );
}

alert(FindIndex({"ob2": "test1"})); // 1
GOTO 0
  • 42,323
  • 22
  • 125
  • 158