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?

16
  • do you need it just once or more than once? Commented Apr 29, 2016 at 20:26
  • 3
    You could, but it won't be any more efficient, it's still javascript Commented Apr 29, 2016 at 20:33
  • 1
    If comparing the stringified objects is enough, you could at least drop all the regex stuff, and just iterate -> jsfiddle.net/g30myfnh/1 Commented Apr 29, 2016 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 Commented Apr 29, 2016 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 Commented Apr 29, 2016 at 20:38

6 Answers 6

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;

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

6 Comments

When you say somewhat reliably, do you mean it may not work in every case?
@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.
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?
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.
@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
|
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>');

5 Comments

How does this help them get the index of the matching object in arrayOfObjs?
@MikeC, now -- by now.
Note that you're relying on order in objects
actually there is only one property. in RL, i would use a array with a fixed order for the properties. or a different approach. ;)
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.
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>

Comments

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

Comments

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))

Comments

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

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.