0

I'm surprised that I can't find an answer to this question on StackOverflow (maybe I'm not searching right).

But basically I'm curious to know if there is something similar to the Array.indexOf() method, but for objects. That is, an efficient method of returning the index(es) of a value within an existing Object.

For example, say I have an object:

var obj = { prop1: "a", prop2: "b", prop3: "c", prop4: "a" };

Now I want to find the index(es) that contain "a", it would be nice to do a obj.indexOf("a") and have it return something like ["prop1", "prop4"]

But this doesn't seem to be an implemented method for objects.

Alternatively, I know I can create a function:

function indexOf(val, obj){
  var indexes = [];

  for (var index in obj){
      if(!obj.hasOwnProperty(index)) continue;

      if(obj[index] == val){
        indexes.push(index);
      }   
   }

   if(!indexes.length) return false;
   else return indexes;
}

indexOf("a", obj); // returns ["prop1","prop4"]

But this kind of feels clunky to iterate over the whole object this way!! Some of the objects I'll be dealing with will be quite huge and the values might be quite large as well.

Is there a better, more efficient way?

Arjun Mehta
  • 2,500
  • 1
  • 24
  • 40
  • Perhaps you should redesign your data structures so you don't need to do this? Anything you need to search for frequently should be a key, not a value. – Barmar Nov 08 '13 at 21:51
  • possible duplicate of [Search a javascript object for a property with a specific value?](http://stackoverflow.com/questions/9422756/search-a-javascript-object-for-a-property-with-a-specific-value) – Barmar Nov 08 '13 at 21:54
  • Maybe the problem with your search is that you looked for index rather than property. – Barmar Nov 08 '13 at 21:54
  • `for..in` is your only option. I would put a hasOwnProperty test in there so you don't get inherited properties back. – RobG Nov 08 '13 at 21:55
  • @Barmar It's a problem, but I am actually using this to look up functions (not simple strings) within an Object so it's my only option. Describing my exact use case would be much too complicated, but there is a reason why I'm looking to do this. – Arjun Mehta Nov 08 '13 at 22:04
  • [Wheeler's Law](http://en.wikipedia.org/wiki/Indirection): All problems in computer science can be solved by another level of indirection. – Barmar Nov 08 '13 at 22:08

1 Answers1

1

If you have a really huge object, you could use a nice weakmap implementation with the complexity of O(1) to store keys per single object. Therefor you have to implement your hash collection, so when setting a key-value pair, you also store the key in the weakmap. I made also some bench. comparison of this custom HashMap vs RawObject search - jsperf

function HashMap() {
    this.__map = new WeakMap;
    this.__hash = {};
}

HashMap.prototype = {
    set: function(key, value){
        this.unset(key);

        if (value == null) 
            return;

        this.__hash[key] = value;

        var keys = this.__map.get(value);
        if (keys == null) 
            this.__map.set(value, keys = []);

        keys.push(key);
    },
    unset: function(key){
        var value = this.__hash[key];
        if (value) {

            var keys = this.__map.get(value),
                index = keys.indexOf(key);

            keys.splice(index, 1);
        }
        this.__hash[key] = void 0;
    },

    get: function(key){
        return this.__hash[key];
    },
    getKeys: function(value){
        return this.__map.get(value);
    }
};

WeakMap uses Object.defineProperty method in its core. For this reason there are some limitations:

  • browsers: IE9+
  • Objects as Values in above HashMap example, because they are used as Keys in WeakMap Collection

But this approach makes a huge performance boost, as there is no need to iterate over the object, to look for a specific value.

tenbits
  • 7,568
  • 5
  • 34
  • 53
  • Super interesting! I am a bit unclear about how this works. When I try to use it in my scenario it appears that one can't use Strings as a Keys? Upon further digging, it seems you are only allowed to use Objects as keys? I wonder if this would work with Functions as values... – Arjun Mehta Nov 09 '13 at 16:33
  • Yes, from the example above you cannt use primitives, _like strings, numbers_ in **Values**, but Objects and **Functions** can - this is the weakmap limitation. But you can convert any string to an object- `str = new String(str)`. This works very simple - we use WeakMap shim - this is a collection where we can have objects and functions as Keys. So we store data in two collections: `property:value`(String:Object/Function) and `value: properties`(Object/Function:Array). So we have much better performance, when we need to get all keys for a value - O(1) instead of O(n) – tenbits Nov 09 '13 at 20:46
  • I just tried this and it works really well! Could you please update your answer to include the limitations and further explanation of how to use this method. Then I will select it as it seems to be a really efficient way. – Arjun Mehta Nov 10 '13 at 12:42
  • PS. I also created a jsperf test case (using your code). Another method I tried was creating a set of index arrays, but also tested against standard iteration. It's amazing how fast it works across browsers!! Firefox is incredibly fast using the hash map. http://jsperf.com/efficient-finding-the-index-of-a-value-within-an-object/2 – Arjun Mehta Nov 10 '13 at 13:47