Logo JavaScript

Fonction « in_array », exercice de style

Flattr this!

Dans la série que j'ai commencé sur les exercices en JavaScript, j'ai pris une très bonne leçon de la part de Jed Schmidt. Nous allons aujourd'hui construire une fonction de type "in_array" comme elle existe en PHP.

Cette fonction n'est pas native en JS et pourtant, je pense ne pas être le seul à avoir déjà pu en avoir l'utilité. Comme pour l'exercice sur le +, je vous donne la solution de suite et j'explique après.

function(a,b){return!!~a.indexOf(b)}

Euh attendez, ça c'est la version minifiée, je vous en fait une version un poil plus lisible 😉

function ( list, needed ) {
    return !!~list.indexOf( needed );
}

Bon ok, c'est à peine mieux. Mais au moins les variables sont plus parlantes.

Alors l'idée est la suivante : Avec EcmaScript 5, la fonction indexOf a été ajouté au prototype des tableaux. Cette fonction qui était propre aux chaînes jusque là va être la clé de tout ça.

Voyons son fonctionnement :

indexOf parcourt son objet appelant à la recherche de l'élément passé en paramètre. Si elle ne le trouve pas, elle retourne -1, sinon la position de l'élément dans la liste. Et on parle en nombre, pas en chaîne.

L'exercice de style reste donc maintenant de renvoyer un booléen. Comme vous le savez certainement, en JavaScript 0 vaut false et toutes les autres valeurs true. C'est balot, c'est pas ce qu'on a. Donc, on va tout inverser. On dispose de l'opérateur ~ qui donne le complément d'une valeur. Ainsi :

~-1: 0
~0: -1
~1: -2

Nous avons donc 0 dans un seul et unique cas, si nous obtenons -1 via indexOf, c'est pratique, 0 équivaut à false.

Ne nous reste plus qu'à générer un vrai booléen à partir de tout ça et c'est ce que nous allons faire en utilisant l'opérateur "!".

Ne l'utiliser qu'une seule fois nous rendrait un résultat inverse de ce que nous souhaiterions et nous devrions alors appeler la méthode "not_in_array". Pas génial, non ? 😉

Nous en arrivons donc simplement à :

function ( list, needed ) {
    return !!~list.indexOf( needed );
}

C'est finalement très simple. Et vu que ça n'utilise que des méthodes natives, c'est plutôt particulièrement rapide. Sur un tableau de 2 millions d'éléments, j'ai un résultat instantané.

Seul souci de cette méthode : elle ne s'applique qu'aux navigateurs modernes, vu qu'elle a besoin que ces derniers implémentent l'ECMAScript 5. Je vous laisse jeter un oeil à la table de compatibilité 😉

Avec quoi on joue maintenant ?

 

Pour information, j'ai mis cette astuce, avec explications, exemples et licence WTFPL sur un gist à disposition de 140byt.es.

 

Edit du 15 novembre 2012 : j'ai fait sauter le gist, utiliser la méthode indexOf() directement est plus pertinent

Flattr this!

A propos de Mathieu

Ingénieur développeur web dans la vente par correspondance B2B, adepte de nouvelles technologies et d'innovation. Vous pouvez aussi me retrouver sur Twitter @mathrobin
Cette entrée a été publiée dans JavaScript, avec comme mot(s)-clef(s) , , . Vous pouvez la mettre en favoris avec ce permalien.
  • http://chez-syl.fr Syl

    Merci pour cette fonction très utile.
    Effectivement lorsque l’on passe du PHP au JS on se retrouve avec beaucoup moins de fonctions native dont celle-ci. :)
    Souhaiterais-tu devenir partenaire ?

  • http://www.mathieurobin.com/ Mathieu

    Attention quand même à son usage, elle reste réservée aux navigateurs implémentant ECMAScript 5, c’est à dire loin de la majorité.

    Pour ta proposition, contacte moi par mail, tu dois l’avoir via les commentaires que je t’ai laissé sur ton blog 😉

  • http://www.b2bweb.fr molokoloco

    Tu préfères noter
    if (inArray(‘a’, [‘a’,’b’,’c’])) {}
    plutôt que
    if ([‘a’,’b’,’c’].indexOf(‘a) >= 0) {}
    Une histoire de goût ? Cependant tu perds l’infos de clé index trouvée et tu ajoutes un proxy sur une méthode native qui marche…
    Nb : ne pas oublier non plus la fonction « lastIndexOf() »
    :)

    Et pour la compatibilité…

    if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt /*, from*/) {
    var len = this.length;
    var from = Number(arguments[1]) || 0;
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
    if (from < 0) from += len;
    for (; from < len; from++) {
    if (from in this && this[from] === elt) return from;
    }
    return -1;
    };
    }

  • http://www.mathieurobin.com/ Mathieu

    Salut, oui effectivement, on y perd beaucoup. Mais l’idée ici était vraiment d’avoir une sorte d’alias plus « parlant ». Et au passage de jouer avec les concepts de ~ pour le changement de valeurs et !! pour le transtypage. Mais on est d’accord, dans l’idée, ça sert à rien et c’est même contre-productif.

  • http://tutos-django.com Guillaume

    Il y a quelque chose que je n’arrive pas a suivre.
    Pourquoi utilise-t’on la combinaison d’opérateurs !!~ et pas seulement ! ?
    indexOf() retourne 0 ou -1, respectivement pour trouvé ou non trouvé.
    Si l’on retourne l’inverse de ces valeurs !0 et !-1, on a bien respectivement true et false.

    Peut-on m’éclairer ? :)

  • http://www.mathieurobin.com/ Mathieu

    Parce que :
    !-1 // false
    !0 // true
    !1 // false

    En faisant ça, toutes les fois où ton élément recherché ne sera pas le premier élément de la liste de comparaison, ton élément sera considéré comme absent

    • http://tutos-django.com Guillaume

      D’accord, je viens de comprendre pourquoi je m’interrogeais sur cette question.
      Tu as ecrit : « indexOf parcourt son objet appelant à la recherche de l’élément passé en paramètre. Si elle le trouve, elle retourne 0, sinon -1″
      En réalité elle retourne -1 si elle ne le trouve pas, et la position du premier matching si elle le trouve (pas uniquement 0).
      L’utilisation de la combinaison d’opérateurs !!~ est donc appropriée :)
      Merci 😉

      • http://www.mathieurobin.com/ Mathieu

        Bien vu, je pensais l’avoir exprimé mieux que ça. Merci !

  • kuroyi

    La méthode proposée ne fonctionne que pour les formes atomiques. L’exemple ci-après renvoie true, false puis false, false alors que l’on s’attendrait également à true, false. La présence d’objets identiques {b:2} de part leurs propriétés et méthodes mais distinctes de part leurs instances n’est pas gérée. La méthode devrait inspecter de façon récursive l’arborescence des composantes des entités à comparer. Voilà un sujet d’exercice… Sinon, j’ai trouvé le site par hasard, c’est propre et bien tenu, la bouffe est bonne, je reviendrai…

    Array.prototype.contains=function(b){return!!~this.indexOf(b)};
    var a=[1,2,3,4];
    // atomic
    console.log(a.contains(2));
    console.log(a.contains(5));
    // non atomic
    var na=[{a:1},{b:2},{c:3}];
    console.log(na.contains({b:2}));
    console.log(na.contains({b:3}));

  • http://pioul.fr Pioul

    C’est une façon de faire, j’aime bien.
    Pour le problème de prise en charge de Array.prototype.indexOf sur les IE moins récents, jQuery.inArray() est là pour ça !

Articles liés