2

This is a stupid question, it feels like one. But mental block is bad right now. :(

My problem is I have an array consisting only of numbers. I want to use that array as a lookup, but the number I pass to lookup a number in the array keeps looking to the array in the index of that number, not whether that number exists in the array.

For example:

var a = [2,4,6,8,10],
b = 2;

if(a[b]){ /* if the number 2 exists in the array a, then do something * }

But that looks at the array value in position 2 (6), not whether the value 2 is in the array. And this makes perfect sense, but (mental block) I can't figure out a way to test whether a number exists in an array of numbers... I even made everything strings, but it does type coercion and the problem persists.

Pulling my hair out here. Please help, thanks. :D

Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
Tom
  • 3,272
  • 3
  • 16
  • 13

5 Answers5

8
if (a.indexOf(2) >= 0)

Note that IE < 9 doesn't have indexOf, so you'll needto add it in case it doesn't exist:

if (!Array.prototype.indexOf)
{
  Array.prototype.indexOf = function(searchElement /*, fromIndex */)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (len === 0)
      return -1;

    var n = 0;
    if (arguments.length > 0)
    {
      n = Number(arguments[1]);
      if (n !== n) // shortcut for verifying if it's NaN
        n = 0;
      else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
    }

    if (n >= len)
      return -1;

    var k = n >= 0
          ? n
          : Math.max(len - Math.abs(n), 0);

    for (; k < len; k++)
    {
      if (k in t && t[k] === searchElement)
        return k;
    }
    return -1;
  };
}
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    Snarky comment: How is this *not* using a loop? – Tomalak Jul 24 '11 at 21:46
  • You forgot to warn them that adding to `Array.prototype` breaks bad third party code (for ... in). @Tomalak he forgot to mention it's impossible without a loop – Raynos Jul 24 '11 at 21:46
  • indexOf!! Dammit I knew there was a really simple way. But I was trying too hard to be simple that I missed the easiest way to do it! :D Thanks SLaks! And thanks for replying with that fix for older browsers. You're a star, bro! – Tom Jul 24 '11 at 21:49
  • @Raynos indexOf works, no loop necessary (not by me anyway). I wanted to stay away from loops out of a desire to optimise my code. :) – Tom Jul 24 '11 at 21:52
  • 1
    @Tom take a look at the fix. It uses a loop. indexOf also uses a loop internally. If you want O(1) lookup use an object. – Raynos Jul 24 '11 at 21:54
  • 1
    @Raynos Well, using a loop in the JavaScript engine I'm ok with that, but in my code I want to avoid it. The JavaScript engines are obviously faster and stuffs. Object lookups are well known to be more expensive, too. Thus ruling that out. – Tom Jul 24 '11 at 21:55
  • 4
    @Tom "object lookups are well known to be more expensive" is a statement that is *crying* for something to back it up. Seriously, this is nonsense. – Tomalak Jul 24 '11 at 21:59
3

If you just want to check whether or not an item is contained by an Array, you can make usage of the nifty bitwise NOT operator along with the .indexOf() method:

if( ~a.indexOf(b) ) {
    // 2 was found, do domething here
}

It's always a good idea to use a shim or library to make sure methods like .indexOf() are available for your Javascript. @SLaks gave you the example for .indexOf() from MDC.

Short explanation why this works:

The bitwise not operator, negates all bits within a byte. That is also true for the positiv/negative bit. Basically it turns the result "-1" into "0". So, if nothing was found we have a falsy value which we want to have at this point. If we match something at the very beginning and get a result of "0" its translated into "-1" which is just fine since its not a falsy value. Any other possible return value is guaranteed a truthy value.

Restuta
  • 5,855
  • 33
  • 44
jAndy
  • 231,737
  • 57
  • 305
  • 359
  • It is a nifty trick, and bit hackery is fun! :P Also, let's not forget, that the bitwise not turns positive to negative, too. So you end up with negative results. – Tom Jul 24 '11 at 21:54
  • @Tom: as I mentioned, that doesn't matter here. We get exactly what we desire. A `truthy` value if the element is contained by the Array and otherwise a `falsy` (0) value. Negative numbers are truthy in this language. – jAndy Jul 24 '11 at 22:06
  • All very cool, but we have to remember that code that is 'too clever' can really just end up reducing readability and maintainability in any real software project. Really, why not just check for `>= 0`. Sure, maybe there are tiny/negligible performance advantages in some cases, but I don't know if this is one -- we still have at least two machine instructions to execute, bit-wise not and branch not zero. @jAndy is there a performance difference? – Peter Jul 25 '11 at 02:18
  • Thanks @jAndy. However, this shows that the performance differences are negligible, so then, what is the advantage? :) – Peter Jul 25 '11 at 04:15
  • @Peter: well, mozilla has a better performance (like 25%) on the bitwise operation. But in general, I think it may be also convinient to use that pattern just for "boolean"-like conditional. If don't care about the actual index. But I understand your point, to an unexperienced programer this can get pretty confusing and you might only want to use it in your own codebases if ever. – jAndy Jul 25 '11 at 12:24
3

If you want a native look-up, use an object, not an array.

var a = {2:0,4:0,6:0,8:0,10:0},
    b = 2;

if (b in a) alert "yay!";

note that I use your array value as the key. Using 0 as the value is arbitrary, it does not matter what you put as the value when you use the in operator.

Use 1 or true if you want to be able to do

if (a[b]) alert "yay!";

but I'd recommend using in as this is both ideomatic and less error-prone.


EDIT: Regarding your notion that array lookup would be faster than object lookup. Try it.

console.log('begin building test array and object'); 
var x = [], y = {};
for (var i=0; i<1000; i++) {
  var n = Math.floor(Math.random() * 1000);
  x.push( n );
  y[n] = true;
}
console.log('finished building test array and object'); 

var foo = 0;

console.log('begin 1,000,000 array search rounds at ' + new Date());
for (var i=0; i<1000000; i++) {
  if (x.indexOf(i % 1000) > -1) foo++;
}
console.log('finished array search rounds at ' + new Date());


console.log('begin 1,000,000 object search rounds at ' + new Date());
for (var i=0; i<1000000; i++) {
  if ((i % 1000) in y) foo++;
}
console.log('finished object search rounds at ' + new Date());
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Of course, but object lookups are more expensive. @Raynos I tried b in a, but that just returns true, which is no good if I want to get at the value. – Tom Jul 24 '11 at 21:52
  • @Tom: Have you *tested* this? Objects are implemented as hash maps, I would be surprised if an array lookup was faster on the average. And what does *"if I want to get the value"* mean - if you can say `if (b in a)` you already know the value (it's `b`), or what am I missing here? – Tomalak Jul 24 '11 at 21:55
  • 1
    @Tom did you just say object lookups are more expensive then for loops? That's wrong. (O(1) is not > O(N)) – Raynos Jul 24 '11 at 21:55
  • @Tom Object lookups will only be more expensive (compared to `Array` looping) if you have very few properties that you are iterating over. Only if the `Array` is tiny would it be less expensive to perform a loop. – Peter Jul 24 '11 at 21:59
  • 1
    @Raynos If you want to return the value I mean. You can't say var c = b in a, and then say c === 2 ... As per speed, check it out here: http://jsperf.com/object-vs-array-perf – Tom Jul 24 '11 at 22:05
  • 1
    @Tom your benchmark is _completely wrong_ : http://jsperf.com/array-loop-vs-object-lookup – Raynos Jul 24 '11 at 22:11
  • 1
    @Tom 1) You are comparing apples and oranges here. The speed test you gave has *nothing* to do with the problem in your question. Indexing into an array to a known position is not the same as searching though it. 2) I'm using Chrome 14, the test results are absolutely the same for both objects and arrays, so your statement doesn't even hold true for the thing the test was designed for. – Tomalak Jul 24 '11 at 22:12
3

A simple loop seems called for here, but-

You can do it without a loop, and without extending IE, if you convert the array to a string.

var a = [2,4,6,8,10], n = 2;

if(RegExp('\\b'+n+'\\b').test(a.join(','))){
// n is in a
}
kennebec
  • 102,654
  • 32
  • 106
  • 127
2

You can simply use .includes() method of arrays:

let a = [2, 4, 6, 8, 10],
    b = 2;

if(a.includes(b)) {
    // your code goes here...
}
Mohammad Usman
  • 37,952
  • 20
  • 92
  • 95