31

I need the index of the first value in the array, that matches a custom compare function.

The very nice underscorej has a "find" function that returns the first value where a function returns true, but I would need this that returns the index instead. Is there a version of indexOf available somewhere, where I can pass a function used to comparing?

Thanks for any suggestions!

Peter T.
  • 2,927
  • 5
  • 33
  • 40
  • 1
    I think your approach in general is wrong here. You do not want functionality to modify default functionality (overloading `===`), you want your own functionality (eg; `myIndexOf`). The former is more disruptive and dangerous than the latter. – Christian Sep 10 '12 at 17:35
  • 2
    lodash has it http://lodash.com/docs#findIndex – Drew LeSueur Jul 30 '14 at 19:14
  • 2
    If your target environment supports ES2015 (or you have a transpile step, eg with Babel), you can use the native Array.prototype.findIndex(). – craigmichaelmartin May 19 '17 at 18:00

8 Answers8

29

Here's the Underscore way to do it - this augments the core Underscore function with one that accepts an iterator function:

// save a reference to the core implementation
var indexOfValue = _.indexOf;

// using .mixin allows both wrapped and unwrapped calls:
// _(array).indexOf(...) and _.indexOf(array, ...)
_.mixin({

    // return the index of the first array element passing a test
    indexOf: function(array, test) {
        // delegate to standard indexOf if the test isn't a function
        if (!_.isFunction(test)) return indexOfValue(array, test);
        // otherwise, look for the index
        for (var x = 0; x < array.length; x++) {
            if (test(array[x])) return x;
        }
        // not found, return fail value
        return -1;
    }

});

_.indexOf([1,2,3], 3); // 2
_.indexOf([1,2,3], function(el) { return el > 2; } ); // 2
nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • 1
    Underscore is overkill. You might want to wrap the whole thing in an IIFE: you've just introduced a dependency on a global variable. Also if there's a bug in your code you've contaminated all code using _.indexOf. – 1983 Sep 04 '14 at 13:00
  • 4
    @mintsauce - The OP referenced Underscore, that's why I offered an Underscore-based solution. W/r/t the global reference, this is a snippet, not a drop-in module; it's the job of the user to wrap it or otherwise set it up in a way appropriate for their application. W/r/t the bug - true, that's why I prefer to write bug-free code :). – nrabinowitz Sep 04 '14 at 22:18
  • 4
    Note: underscore.js added a [findIndex](http://underscorejs.org/#findIndex) function since the original post and answer. – Ben Jul 02 '15 at 22:27
15

There's a standard function in ECMAScript 2015 for Array.prototype.findIndex(). Currently it's implemented in all major browsers apart from Internet Explorer.

Here's a polyfill, courtesy of the Mozilla Developer Network:

// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
if (!Array.prototype.findIndex) {
  Object.defineProperty(Array.prototype, 'findIndex', {
    value: function(predicate) {
     // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }

      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
      var thisArg = arguments[1];

      // 5. Let k be 0.
      var k = 0;

      // 6. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kValue be ? Get(O, Pk).
        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
        // d. If testResult is true, return k.
        var kValue = o[k];
        if (predicate.call(thisArg, kValue, k, o)) {
          return k;
        }
        // e. Increase k by 1.
        k++;
      }

      // 7. Return -1.
      return -1;
    },
    configurable: true,
    writable: true
  });
}
Jake Thakur
  • 156
  • 3
  • 10
Husky
  • 5,757
  • 2
  • 46
  • 41
7

You could do something like this:

Array.prototype.myIndexOf = function(f)
{
    for(var i=0; i<this.length; ++i)
    {
        if( f(this[i]) )
            return i;
    }
    return -1;
};

Regarding Christian's comment: if you override a standard JavaScript method with a custom one with a different the same signature and different functionality, bad thing will likely happen. This is especially true if you're pulling in 3rd party libraries which may depend on the original, say, Array.proto.indexOf. So yeah, you probably want to call it something else.

  • 2
    Thanks for the concern. I also believe in second chances. ;) Please do highlight why `Array.prototype.indexOf(function)` is the wrong approach, and I'll give you that upvote. – Christian Sep 10 '12 at 17:36
  • Thanks for this one. I could use this one without adding it to Array.prototype. – Peter T. Sep 10 '12 at 17:41
  • It's better not to add to Array.prototype at all unless you are providing a shim for a function that is in the Standard but not supported by a particular implementation. – 1983 Sep 04 '14 at 12:32
3

As others have noted, easy enough to roll your own, which you can keep short and simple for your particular use case:

// Find the index of the first element in array
// meeting specified condition.
//
var findIndex = function(arr, cond) {
  var i, x;
  for (i in arr) {
    x = arr[i];
    if (cond(x)) return parseInt(i);
  }
};

var moreThanTwo = function(x) { return x > 2 }
var i = findIndex([1, 2, 3, 4], moreThanTwo)

Or if you're a CoffeeScripter:

findIndex = (arr, cond) ->
  for i, x of arr
    return parseInt(i) if cond(x)
joyrexus
  • 390
  • 1
  • 3
  • 8
1

The javascript array method filter returns a subset of the array that return true from the function passed.

var arr= [1, 2, 3, 4, 5, 6],
first= arr.filter(function(itm){
    return itm>3;
})[0];
alert(first);

if you must support IE before #9 you can 'shim' Array.prototype.filter-

Array.prototype.filter= Array.prototype.filter || function(fun, scope){
    var T= this, A= [], i= 0, itm, L= T.length;
    if(typeof fun== 'function'){
        while(i<L){
            if(i in T){
                itm= T[i];
                if(fun.call(scope, itm, i, T)) A[A.length]= itm;
            }
            ++i;
        }
    }
    return A;
}
kennebec
  • 102,654
  • 32
  • 106
  • 127
  • thanks, building a new subset array might slow down performance - what do you think? – Peter T. Sep 17 '12 at 13:46
  • I think this is the simplest, most elegant answer. Doesn't require defining any new methods, underscore, or a polyfill or anything. I'm talking strictly about simplicity, not performance. – Lane Rettig Feb 25 '16 at 20:35
1

How about such find function ?

(function () {
  if (!Array.prototype._find) {
    Array.prototype._find = function (value) {
      var i = -1, j = this.length;
      if (typeof(value)=="function") 
         for(; (++i < j) && !value(this[i]););
      else
         for(; (++i < j) && !(this[i] === value););

      return i!=j ? i : -1;
    }
  }
}());
23W
  • 1,413
  • 18
  • 37
1

Here comes the coffeescript version of nrabinowitz's code.

# save a reference to the core implementation
indexOfValue = _.indexOf

# using .mixin allows both wrapped and unwrapped calls:
# _(array).indexOf(...) and _.indexOf(array, ...)
_.mixin ({
    # return the index of the first array element passing a test
    indexOf: (array, test) ->
        # delegate to standard indexOf if the test isn't a function
        if (!_.isFunction(test))
            return indexOfValue(array, test)
        # otherwise, look for the index
        for item, i in array
            return i if (test(item))
        # not found, return fail value
        return -1
})
Community
  • 1
  • 1
Shiva Huang
  • 91
  • 1
  • 6
0

using underscore I came up with something copied from their find implementation using _.any:

findIndex = function (obj, iterator, context) {
    var idx;
    _.any(obj, function (value, index, list) {
        if (iterator.call(context, value, index, list)) {
            idx = index;
            return true;
        }
    });
    return idx;
};

What do you think - do you have any better solutions?

Peter T.
  • 2,927
  • 5
  • 33
  • 40
  • I don't know what `_.any` means. I guess you're using some framework? If so, be nice to us and tell us your secret ;). – Christian Sep 10 '12 at 17:41
  • 1
    You should not use the extra function with `any`, it slows the method down. Just use a simple for-loop – Bergi Sep 10 '12 at 17:44