Question Comment comparer des tableaux en JavaScript?


Je voudrais comparer deux tableaux ... idéalement, efficacement. Rien d'extraordinaire, juste true si elles sont identiques, et false si non. Sans surprise, l'opérateur de comparaison ne semble pas fonctionner.

var a1 = [1,2,3];
var a2 = [1,2,3];
console.log(a1==a2);    // Returns false
console.log(JSON.stringify(a1)==JSON.stringify(a2));    // Returns true

Le codage JSON de chaque tableau le fait, mais existe-t-il un moyen plus rapide ou «meilleur» de simplement comparer les tableaux sans avoir à parcourir chaque valeur?


670
2017-10-20 14:27


origine


Réponses:


Pour comparer des tableaux, parcourez-les et comparez chaque valeur:

Comparaison de tableaux:

// Warn if overriding existing method
if(Array.prototype.equals)
    console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
// attach the .equals method to Array's prototype to call it on any array
Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;       
        }           
        else if (this[i] != array[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }           
    }       
    return true;
}
// Hide method from for-in loops
Object.defineProperty(Array.prototype, "equals", {enumerable: false});

Usage:

[1, 2, [3, 4]].equals([1, 2, [3, 2]]) === false;
[1, "2,3"].equals([1, 2, 3]) === false;
[1, 2, [3, 4]].equals([1, 2, [3, 4]]) === true;
[1, 2, 1, 2].equals([1, 2, 1, 2]) === true;

Vous pouvez dire "Mais il est beaucoup plus rapide de comparer des cordes - pas de boucles ..."bien, alors vous devriez noter qu'il y a des boucles ARE: une première boucle récursive qui convertit un tableau en une chaîne et deuxièmement, qui compare deux chaînes. est plus rapide que l'utilisation de la chaîne.

Je crois que de plus grandes quantités de données devraient toujours être stockées dans des tableaux, pas dans des objets. Cependant, si vous utilisez des objets, ils peuvent aussi être partiellement comparés.
Voici comment:

Comparer des objets:

J'ai déclaré ci-dessus, que deux objets instances ne seront jamais égaux, même s'ils contiennent les mêmes données pour le moment:

({a:1, foo:"bar", numberOfTheBeast: 666}) == ({a:1, foo:"bar", numberOfTheBeast: 666})  //false

Cela a une raison, car il peut y avoir, par exemple variables privées dans les objets.

Cependant, si vous utilisez simplement la structure d'objet pour contenir des données, la comparaison est toujours possible:

Object.prototype.equals = function(object2) {
    //For the first loop, we only check for types
    for (propName in this) {
        //Check for inherited methods and properties - like .equals itself
        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
        //Return false if the return value is different
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        //Check instance type
        else if (typeof this[propName] != typeof object2[propName]) {
            //Different types => not equal
            return false;
        }
    }
    //Now a deeper check using other objects property names
    for(propName in object2) {
        //We must check instances anyway, there may be a property that only exists in object2
            //I wonder, if remembering the checked values from the first loop would be faster or not 
        if (this.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) {
            return false;
        }
        else if (typeof this[propName] != typeof object2[propName]) {
            return false;
        }
        //If the property is inherited, do not check any more (it must be equa if both objects inherit it)
        if(!this.hasOwnProperty(propName))
          continue;

        //Now the detail check and recursion

        //This returns the script back to the array comparing
        /**REQUIRES Array.equals**/
        if (this[propName] instanceof Array && object2[propName] instanceof Array) {
                   // recurse into the nested arrays
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        else if (this[propName] instanceof Object && object2[propName] instanceof Object) {
                   // recurse into another objects
                   //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
           if (!this[propName].equals(object2[propName]))
                        return false;
        }
        //Normal value comparison for strings and numbers
        else if(this[propName] != object2[propName]) {
           return false;
        }
    }
    //If everything passed, let's say YES
    return true;
}  

Cependant, rappelez-vous que celui-ci sert à comparer des données JSON, pas des instances de classe et d'autres choses. Si vous voulez comparer des objets plus compliqués, regardez cette réponse et sa fonction superlong.
Pour que cela fonctionne avec Array.equals vous devez modifier la fonction d'origine un peu:

...
    // Check if we have nested arrays
    if (this[i] instanceof Array && array[i] instanceof Array) {
        // recurse into the nested arrays
        if (!this[i].equals(array[i]))
            return false;
    }
    /**REQUIRES OBJECT COMPARE**/
    else if (this[i] instanceof Object && array[i] instanceof Object) {
        // recurse into another objects
        //console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
        if (!this[i].equals(array[i]))
            return false;
        }
    else if (this[i] != array[i]) {
...

J'ai fait un petit outil de test pour les deux fonctions.

Bonus: tableaux imbriqués avec indexOf et contains

Samy Bencherif a préparé fonctions utiles pour le cas où vous recherchez un objet spécifique dans des tableaux imbriqués, qui sont disponibles ici: https://jsfiddle.net/SamyBencherif/8352y6yw/


722
2018-02-13 12:49



Bien que cela ne fonctionne que pour les tableaux scalaires (voir la note ci-dessous), il est court:

array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})

Rr, dans ECMAScript 6 / CoffeeScript / TypeScript avec des fonctions de flèche:

array1.length === array2.length && array1.every((value, index) => value === array2[index])

(Note: «scalaire» signifie ici des valeurs qui peuvent être comparées directement en utilisant === . Donc: nombres, chaînes, objets par référence, fonctions par référence. Voir la référence MDN pour plus d'informations sur les opérateurs de comparaison).


230
2017-11-02 20:56



J'aime utiliser la bibliothèque Underscore pour les projets de codage de tableaux / objets lourds ... en Underscore et Lodash si vous comparez des tableaux ou des objets, cela ressemble à ceci:

_.isEqual(array1, array2)   // returns a boolean
_.isEqual(object1, object2) // returns a boolean

151
2018-02-03 14:20



On ne sait pas ce que vous entendez par "identique". Par exemple, sont les tableaux a et b ci-dessous identique (notez les tableaux imbriqués)?

var a = ["foo", ["bar"]], b = ["foo", ["bar"]];

Voici une fonction de comparaison de tableau optimisée qui compare les éléments correspondants de chaque tableau à tour de rôle en utilisant une égalité stricte et ne fait pas de comparaison récursive d'éléments de tableau qui sont eux-mêmes des tableaux, ce qui signifie que pour l'exemple ci-dessus, arraysIdentical(a, b)retournerais false. Cela fonctionne dans le cas général, lequel JSON- et join()les solutions basées sur:

function arraysIdentical(a, b) {
    var i = a.length;
    if (i != b.length) return false;
    while (i--) {
        if (a[i] !== b[i]) return false;
    }
    return true;
};

60
2017-10-20 14:46



Je pense que c'est la façon la plus simple de le faire en utilisant JSON stringify, et c'est peut-être la meilleure solution dans certaines situations:

JSON.stringify(a1) === JSON.stringify(a2);

Cela convertit les objets a1 et a2 en chaînes afin qu'ils puissent être comparés. L'ordre est important dans la plupart des cas, car cela peut trier l'objet en utilisant un algorithme de tri montré dans l'une des réponses ci-dessus.

Veuillez noter que vous ne comparez plus l'objet mais la représentation sous forme de chaîne de l'objet. Ce n'est peut-être pas exactement ce que vous voulez.


58
2018-05-09 02:38



La manière pratique

Je pense qu'il est faux de dire qu'une implémentation particulière est «la bonne manière» si elle est seulement «juste» («correcte») par opposition à une «mauvaise» solution. La solution de Tomáš est une nette amélioration par rapport à la comparaison de tableaux basée sur les chaînes, mais cela ne signifie pas que c'est objectivement "correct". Quel est droite en tous cas? Est-ce le plus rapide? Est-ce le plus flexible? Est-ce le plus facile à comprendre? Est-ce le plus rapide à déboguer? Utilise-t-il le moins d'opérations? Est-ce que cela a des effets secondaires? Aucune solution ne peut avoir le meilleur de toutes les choses.

Tomáš pourrait dire que sa solution est rapide mais je dirais aussi que c'est inutilement compliqué. Il essaie d'être une solution tout-en-un qui fonctionne pour tous les tableaux, imbriqués ou non. En fait, il accepte même plus que des tableaux comme entrée et tente toujours de donner une réponse "valide".


Les génériques offrent la réutilisabilité

Ma réponse abordera le problème différemment. Je vais commencer avec un générique arrayCompare procédure qui ne concerne que le passage à travers les tableaux. De là, nous allons construire nos autres fonctions de comparaison de base comme arrayEqual et arrayDeepEqual, etc

// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
  x === undefined && y === undefined
    ? true
    : Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)

À mon avis, le meilleur type de code n'a même pas besoin de commentaires, et ce n'est pas une exception. Il se passe si peu de choses ici que vous pouvez comprendre le comportement de cette procédure avec presque aucun effort du tout. Bien sûr, une partie de la syntaxe ES6 peut vous sembler étrangère, mais c'est seulement parce que ES6 est relativement nouveau.

Comme le type le suggère, arrayCompare prend la fonction de comparaison, fet deux tableaux d'entrée, xs et ys. Pour la plupart, tout ce que nous faisons est d'appeler f (x) (y) pour chaque élément dans les tableaux d'entrée. Nous retournons tôt false si l'utilisateur a défini f résultats false - grâce à &&l'évaluation de court-circuit. Alors oui, cela signifie que le comparateur peut arrêter l'itération tôt et empêcher le bouclage à travers le reste du tableau d'entrée lorsqu'il n'est pas nécessaire.


Comparaison stricte

Ensuite, en utilisant notre arrayCompare fonction, nous pouvons facilement créer d'autres fonctions dont nous pourrions avoir besoin. Nous allons commencer avec l'élémentaire arrayEqual ...

// equal :: a -> a -> Bool
const equal = x => y =>
  x === y // notice: triple equal

// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
  arrayCompare (equal)

const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys))      //=> true
// (1 === 1) && (2 === 2) && (3 === 3)  //=> true

const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs))      //=> false
// (1 === '1')                          //=> false

Aussi simple que cela. arrayEqual peut être défini avec arrayCompare et une fonction de comparaison qui compare a à b en utilisant === (pour une égalité stricte).

Notez que nous définissons également equal comme c'est propre fonction. Cela met en évidence le rôle de arrayCompare comme une fonction d'ordre supérieur pour utiliser notre comparateur de premier ordre dans le contexte d'un autre type de données (Array).


Comparaison lâche

Nous pourrions tout aussi facilement définir arrayLooseEqual utilisant un == au lieu. Maintenant, en comparant 1 (Nombre) à '1' (Chaîne), le résultat sera true ...

// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
  x == y // notice: double equal

// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
  arrayCompare (looseEqual)

const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys))    //=> true
// (1 == '1') && (2 == '2') && (3 == '3')  //=> true

Comparaison profonde (récursive)

Vous avez probablement remarqué que ce n'est qu'une comparaison superficielle. La solution de Tomáš est sûrement "la bonne voie" parce qu'elle implique une comparaison profonde implicite, n'est-ce pas?

Eh bien notre arrayCompare La procédure est assez polyvalente pour être utilisée de manière à faire passer un test d'égalité en profondeur ...

// isArray :: a -> Bool
const isArray =
  Array.isArray

// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
  arrayCompare (a => b =>
    isArray (a) && isArray (b)
      ? arrayDeepCompare (f) (a) (b)
      : f (a) (b))

const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3')         //=> false

console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3')                 //=> true

Aussi simple que cela. Nous construisons un comparateur profond en utilisant un autre fonction d'ordre supérieur. Cette fois nous emballons arrayCompare en utilisant un comparateur personnalisé qui permettra de vérifier si a et b sont des tableaux. Si oui, réappliquez arrayDeepCompare comparer autrement a et b au comparateur spécifié par l'utilisateur (f). Cela nous permet de séparer le comportement de la comparaison profonde de la façon dont nous comparons réellement les éléments individuels. C'est à dire, comme l'exemple ci-dessus montre, nous pouvons comparer en profondeur en utilisant equal, looseEqual, ou tout autre comparateur que nous faisons.

Car arrayDeepCompare est curry, nous pouvons l'appliquer partiellement comme nous l'avons fait dans les exemples précédents

// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
  arrayDeepCompare (equal)

// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
  arrayDeepCompare (looseEqual)

Pour moi, c'est déjà une nette amélioration par rapport à la solution de Tomáš parce que je peux explicitement choisissez une comparaison superficielle ou profonde pour mes tableaux, au besoin.


Comparaison d'objets (exemple)

Maintenant, que faire si vous avez un tableau d'objets ou quelque chose? Peut-être que vous voulez considérer ces tableaux comme "égaux" si chaque objet a le même id valeur …

// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
  x.id !== undefined && x.id === y.id

// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
  arrayCompare (idEqual)

const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2)            //=> true

const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6)            //=> false

Aussi simple que cela. Ici, j'ai utilisé des objets JS vanilles, mais ce type de comparateur pourrait fonctionner pour tout type d'objet; même vos objets personnalisés. La solution de Tomáš devrait être complètement retravaillée pour supporter ce genre de test d'égalité

Deep tableau avec des objets? Pas de problème. Nous avons construit des fonctions génériques très polyvalentes, de sorte qu'elles fonctionnent dans une grande variété de cas d'utilisation.

const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys))     //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true

Comparaison arbitraire (exemple)

Ou si vous vouliez faire une sorte de comparaison complètement arbitraire? Peut-être que je veux savoir si chaque x est supérieur à chaque y ...

// gt :: Number -> Number -> Bool
const gt = x => y =>
  x > y

// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)

const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys))     //=> true
// (5 > 2) && (10 > 4) && (20 > 8)  //=> true

const zs = [6,12,24]
console.log (arrayGt (xs) (zs))     //=> false
// (5 > 6)                          //=> false

Moins est plus

Vous pouvez voir que nous en faisons plus avec moins de code. Il n'y a rien de compliqué à propos de arrayCompare lui-même et chacun des comparateurs personnalisés que nous avons fait ont une implémentation très simple.

Avec facilité, nous pouvons définir exactement comment nous souhaitons comparer deux tableaux - peu profond, profond, strict, lâche, une propriété d'objet, ou un calcul arbitraire, ou une combinaison de ceux-ci - tout en utilisant une procédure, arrayCompare. Peut-être même rêver d'un RegExp comparateur! Je sais comment les enfants adorent ces expressions rationnelles ...

Est-ce le plus rapide? Nan. Mais il n'a probablement pas besoin d'être non plus. Si la vitesse est la seule mesure utilisée pour mesurer la qualité de notre code, beaucoup de code vraiment génial serait jeté - C'est pourquoi j'appelle cette approche La manière pratique. Ou peut-être pour être plus juste, UNE Manière pratique Cette description convient à cette réponse parce que je ne dis pas que cette réponse est seulement pratique par rapport à une autre réponse; c'est objectivement vrai. Nous avons atteint un haut degré de praticité avec très peu de code qui est très facile à raisonner. Aucun autre code ne peut dire que nous n'avons pas gagné cette description.

Cela en fait-il la "bonne" solution pour vous? C'est parti pour toi decider. Et personne d'autre ne peut le faire pour vous. vous seul savez quels sont vos besoins. Dans presque tous les cas, j'apprécie le code simple, pratique et polyvalent sur un type intelligent et rapide. Ce que vous appréciez peut différer, alors choisissez ce qui fonctionne pour vous.


modifier

Ma vieille réponse était plus axée sur la décomposition arrayEqual dans de minuscules procédures. C'est un exercice intéressant, mais pas vraiment le meilleur (le plus pratique) pour aborder ce problème. Si vous êtes intéressé, vous pouvez voir cet historique de révision.


40
2017-12-13 22:10



À partir de la réponse de Tomáš Zato, je suis d'accord pour dire que le passage le plus rapide à travers les tableaux est le plus rapide. De plus (comme d'autres l'ont déjà dit), la fonction devrait être appelée égale / égale, pas comparer. À la lumière de cela, j'ai modifié la fonction pour gérer la comparaison des tableaux pour la similarité - c'est-à-dire qu'ils ont les mêmes éléments, mais hors d'usage - pour un usage personnel, et j'ai pensé que je les lirais à tout le monde.

Array.prototype.equals = function (array, strict) {
    if (!array)
        return false;

    if (arguments.length == 1)
        strict = true;

    if (this.length != array.length)
        return false;

    for (var i = 0; i < this.length; i++) {
        if (this[i] instanceof Array && array[i] instanceof Array) {
            if (!this[i].equals(array[i], strict))
                return false;
        }
        else if (strict && this[i] != array[i]) {
            return false;
        }
        else if (!strict) {
            return this.sort().equals(array.sort(), true);
        }
    }
    return true;
}

Cette fonction prend un paramètre supplémentaire de strict qui vaut par défaut true. Ce paramètre strict définit si les tableaux doivent être entièrement égaux dans les deux contenus et dans l'ordre de ces contenus, ou tout simplement contenir le même contenu.

Exemple:

var arr1 = [1, 2, 3, 4];
var arr2 = [2, 1, 4, 3];  // Loosely equal to 1
var arr3 = [2, 2, 3, 4];  // Not equal to 1
var arr4 = [1, 2, 3, 4];  // Strictly equal to 1

arr1.equals(arr2);         // false
arr1.equals(arr2, false);  // true
arr1.equals(arr3);         // false
arr1.equals(arr3, false);  // false
arr1.equals(arr4);         // true
arr1.equals(arr4, false);  // true

J'ai aussi écrit un rapide jsfiddle avec la fonction et cet exemple:
http://jsfiddle.net/Roundaround/DLkxX/


27
2017-07-31 20:25



Dans l'esprit de la question originale:

Je voudrais comparer deux tableaux ... idéalement, efficacement. Rien   fantaisie, juste vrai si elles sont identiques, et faux si non.

J'ai effectué des tests de performance sur certaines des suggestions les plus simples proposées ici avec les suivantes résultats (rapide à lent):

tandis que (67%) par Tim Down

var i = a1.length;
while (i--) {
    if (a1[i] !== a2[i]) return false;
}
return true

chaque (69%) par utilisateur2782196

a1.every((v,i)=> v === a2[i]);

réduire (74%) par DEIs

a1.reduce((a, b) => a && a2.includes(b), true);

joindre & toString (78%) par Gaizka Allende & vivek

a1.join('') === a2.join('');

a1.toString() === a2.toString();

moitié toString (90%) par Victor Palomo

a1 == a2.toString();

stringifier (100%) par radtek

JSON.stringify(a1) === JSON.stringify(a2);

Remarque les exemples ci-dessous supposent que les tableaux sont triés, des tableaux unidimensionnels. .length comparaison a été supprimée pour un benchmark commun (ajouter a1.length === a2.length à l'une des suggestions et vous obtiendrez une augmentation de performance de ~ 10%). Choisissez les solutions qui vous conviennent le mieux en connaissant la vitesse et la limitation de chacune.

Note non liée: Il est intéressant de voir que les gens obtiennent tous les suffrages de John Waynes, le déclencheur du vote, sur des réponses parfaitement légitimes à cette question.


24
2018-02-12 09:48