Pregunta ¿Cómo comparar matrices en JavaScript?


Me gustaría comparar dos matrices ... idealmente, de manera eficiente. Nada de lujo, solo true si son idénticos, y false si no. No es sorprendente que el operador de comparación no parezca funcionar.

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

La codificación JSON de cada matriz sí lo hace, pero ¿existe una forma más rápida o "mejor" de simplemente comparar matrices sin tener que iterar a través de cada valor?


670
2017-10-20 14:27


origen


Respuestas:


Para comparar matrices, recorralas y compare cada valor:

Comparación de matrices:

// 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});

Uso:

[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;

Podrías decir "Pero es mucho más rápido comparar cadenas, sin bucles ..."bueno, entonces deberías notar que hay bucles ARE. Primero bucle recursivo que convierte Array en string y segundo, que compara dos strings. Entonces este método es más rápido que el uso de una cuerda.

Creo que grandes cantidades de datos siempre deben almacenarse en matrices, no en objetos. Sin embargo, si usas objetos, también se pueden comparar parcialmente.
Así es cómo:

Comparando objetos:

He dicho anteriormente, que dos objetos instancias nunca serán iguales, incluso si contienen la misma información en este momento:

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

Esto tiene una razón, ya que puede haber, por ejemplo variables privadas dentro de los objetos.

Sin embargo, si solo usa la estructura de objeto para contener datos, aún es posible comparar:

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;
}  

Sin embargo, recuerde que este es para servir al comparar JSON como datos, no instancias de clase y otras cosas. Si quiere comparar objetos complicados, mire esta respuesta y su función superlong.
Para hacer que esto funcione Array.equals debe editar la función original un poco:

...
    // 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]) {
...

Hice una pequeña herramienta de prueba para ambas funciones.

Bonus: matrices anidadas con indexOf y contains

Samy Bencherif ha preparado funciones útiles para el caso de que esté buscando un objeto específico en matrices anidadas, que están disponibles aquí: https://jsfiddle.net/SamyBencherif/8352y6yw/


722
2018-02-13 12:49



Si bien esto solo funciona para matrices escalares (ver nota a continuación), es breve:

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

Rr, en ECMAScript 6 / CoffeeScript / TypeScript con funciones de flecha:

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

(Nota: 'escalar' aquí significa valores que se pueden comparar directamente usando === . Entonces: números, cadenas, objetos por referencia, funciones por referencia. Ver la referencia MDN para más información sobre los operadores de comparación).


230
2017-11-02 20:56



Me gusta utilizar la biblioteca Underscore para proyectos de codificación pesados ​​de matriz / objeto ... en Underscore y Lodash, ya sea que esté comparando matrices u objetos, se ve así:

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

151
2018-02-03 14:20



No está claro a qué te refieres con "idéntico". Por ejemplo, son las matrices a y b debajo idéntico (observe las matrices anidadas)?

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

Aquí hay una función de comparación de matriz optimizada que compara los elementos correspondientes de cada matriz a su vez usando estricta igualdad y no hace una comparación recursiva de elementos de matriz que son en sí mismos matrices, lo que significa que para el ejemplo anterior, arraysIdentical(a, b)volvería false. Funciona en el caso general, que JSON- y join()las soluciones basadas en la base no:

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



Esta es la forma más simple de hacerlo usando JSON stringify, y puede ser la mejor solución en algunas situaciones:

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

Esto convierte los objetos a1 y a2 en cuerdas para que puedan ser comparados. El orden es importante en la mayoría de los casos, ya que puede clasificar el objeto utilizando un algoritmo de ordenamiento que se muestra en una de las respuestas anteriores.

Tenga en cuenta que ya no está comparando el objeto, sino la representación de cadena del objeto. Puede que no sea exactamente lo que quieres.


58
2018-05-09 02:38



La forma práctica

Creo que es un error decir que una implementación en particular es "La manera correcta" si solo es "correcta" ("correcta") en contraste con una solución "incorrecta". La solución de Tomáš es una clara mejora sobre la comparación de matriz basada en cadenas, pero eso no significa que sea objetivamente "correcta". Que es derecho ¿de todas formas? ¿Es el más rápido? ¿Es el más flexible? ¿Es el más fácil de comprender? ¿Es el más rápido de depurar? ¿Utiliza la menor cantidad de operaciones? ¿Tiene efectos secundarios? Ninguna solución puede tener lo mejor de todas las cosas.

Tomáš podría decir que su solución es rápida, pero también diría que es innecesariamente complicada. Intenta ser una solución todo en uno que funcione para todas las matrices, anidadas o no. De hecho, incluso acepta más que solo arrays como entrada y aún intenta dar una respuesta "válida".


Los genéricos ofrecen reutilización

Mi respuesta abordará el problema de manera diferente. Comenzaré con un genérico arrayCompare procedimiento que solo está relacionado con recorrer las matrices. A partir de ahí, crearemos nuestras otras funciones básicas de comparación como arrayEqual y 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)

En mi opinión, el mejor tipo de código ni siquiera necesita comentarios, y esta no es una excepción. Aquí sucede tan poco que puede comprender el comportamiento de este procedimiento casi sin esfuerzo. Claro, algo de la sintaxis de ES6 puede parecerle extraño ahora, pero eso es solo porque ES6 es relativamente nuevo.

Como el tipo sugiere, arrayCompare toma la función de comparación, fy dos matrices de entrada xs y ys. En su mayor parte, todo lo que hacemos es llamar f (x) (y) para cada elemento en las matrices de entrada. Volvemos temprano false si el definido por el usuario f devoluciones false - gracias a &&evaluación de cortocircuito. Así que sí, esto significa que el comparador puede detener la iteración antes de tiempo y evitar el bucle a través del resto de la matriz de entrada cuando sea innecesario.


Estricta comparación

Luego, usando nuestro arrayCompare función, podemos crear fácilmente otras funciones que podamos necesitar. Comenzaremos con la primaria 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

Simple como eso. arrayEqual se puede definir con arrayCompare y una función de comparación que compara a a b utilizando === (por estricta igualdad).

Tenga en cuenta que también definimos equal como su propia función. Esto destaca el papel de arrayCompare como una función de orden superior para utilizar nuestro comparador de primer orden en el contexto de otro tipo de datos (Array).


Comparación suelta

Podríamos definirlo tan fácilmente arrayLooseEqual usando un == en lugar. Ahora cuando comparas 1 (Número) a '1' (String), el resultado será 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

Comparación profunda (recursiva)

Probablemente hayas notado que esto es solo una comparación superficial. Seguramente la solución de Tomáš es "The Right Way" porque hace una comparación profunda implícita, ¿verdad?

Bueno, nuestro arrayCompare El procedimiento es lo suficientemente versátil como para usarlo de una manera que hace que una prueba de igualdad profunda sea muy sencilla ...

// 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

Simple como eso. Construimos un comparador profundo usando otro función de orden superior. Esta vez estamos envolviendo arrayCompare usando un comparador personalizado que verificará si a y b son matrices. Si es así, vuelva a solicitar arrayDeepCompare de lo contrario compara a y b al comparador especificado por el usuario (f) Esto nos permite mantener el comportamiento de comparación profunda separado de la forma en que realmente comparamos los elementos individuales. Es decir, como muestra el ejemplo anterior, podemos comparar profundamente usando equal, looseEqual, o cualquier otro comparador que hagamos.

Porque arrayDeepCompare está al curry, podemos aplicarlo parcialmente como lo hicimos en los ejemplos anteriores también

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

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

Para mí, esto ya es una clara mejora sobre la solución de Tomáš porque puedo explícitamente elija una comparación superficial o profunda para mis matrices, según sea necesario.


Comparación de objetos (ejemplo)

Ahora, ¿qué pasa si tienes una variedad de objetos o algo? Tal vez quiera considerar esas matrices como "iguales" si cada objeto tiene el mismo id valor ...

// 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

Simple como eso. Aquí he usado objetos vanilla JS, pero este tipo de comparador podría funcionar para alguna tipo de objeto; incluso tus objetos personalizados La solución de Tomáš necesitaría ser completamente modificada para apoyar este tipo de prueba de igualdad

¿Arreglo profundo con objetos? No es un problema. Creamos funciones genéricas altamente versátiles, por lo que funcionarán en una amplia variedad de casos de uso.

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

Comparación arbitraria (ejemplo)

¿O qué tal si quisieras hacer algún otro tipo de comparación completamente arbitraria? Tal vez quiero saber si cada uno x es mayor que cada 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

Menos es más

Puedes ver que en realidad estamos haciendo más con menos código. No hay nada complicado sobre arrayCompare en sí y cada uno de los comparadores personalizados que hemos realizado tiene una implementación muy simple.

Con facilidad, podemos definir exactamente cómo queremos que se comparen dos matrices: superficial, profunda, estricta, suelta, alguna propiedad del objeto, o algún cálculo arbitrario, o cualquier combinación de estos, todo usando un procedimiento, arrayCompare. Tal vez incluso soñar con un RegExp comparador! Sé cómo a los niños les encantan esas expresiones regulares ...

¿Es el más rápido? Nop. Pero probablemente tampoco sea necesario. Si la velocidad es la única métrica utilizada para medir la calidad de nuestro código, se descartaría una gran cantidad de código realmente genial. Es por eso que estoy llamando a este enfoque. La forma práctica. O tal vez para ser más justo, UN Manera practica. Esta descripción es adecuada para esta respuesta porque no estoy diciendo que esta respuesta sea solo práctica en comparación con alguna otra respuesta; es objetivamente verdadero Hemos alcanzado un alto grado de practicidad con muy poco código que sea muy fácil de razonar. Ningún otro código puede decir que no nos hemos ganado esta descripción.

¿Esa es la solución "correcta" para ti? Eso es para  para decidir. Y nadie más puede hacer eso por usted; solo tú sabes cuáles son tus necesidades. En casi todos los casos, valoro el código directo, práctico y versátil sobre el tipo inteligente y rápido. Lo que valoras puede diferir, así que elige lo que funcione para ti.


Editar

Mi vieja respuesta estaba más centrada en la descomposición arrayEqual en pequeños procedimientos. Es un ejercicio interesante, pero no es realmente la mejor (la más práctica) forma de abordar este problema. Si está interesado, puede ver este historial de revisión.


40
2017-12-13 22:10



Partiendo de la respuesta de Tomáš Zato, estoy de acuerdo en que solo iterar a través de las matrices es el más rápido. Además (como otros ya han indicado), la función debe llamarse igual / igual, no comparar. A la luz de esto, modifiqué la función para manejar la comparación de matrices en busca de similitud, es decir, tienen los mismos elementos, pero fuera de servicio, para uso personal, y pensé que lo pondría aquí para que todos lo vean.

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;
}

Esta función toma un parámetro adicional de strict que por defecto es verdadero. Este parámetro estricto define si las matrices deben ser totalmente iguales tanto en el contenido como en el orden de esos contenidos, o simplemente contienen los mismos contenidos.

Ejemplo:

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

También escribí un jsfiddle rápido con la función y este ejemplo:
http://jsfiddle.net/Roundaround/DLkxX/


27
2017-07-31 20:25



En el espíritu de la pregunta original:

Me gustaría comparar dos matrices ... idealmente, eficientemente. Nada   lujoso, solo es cierto si son idénticos, y falso si no es así.

He estado ejecutando pruebas de rendimiento en algunas de las sugerencias más simples propuestas aquí con los siguientes resultados (rápido a lento):

mientras (67%) por Tim Down

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

cada (69%) por user2782196

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

reducir (74%) por DEIs

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

unirse & Encadenar (78%) por Gaizka Allende & vivek

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

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

medio toString (90%) por Victor Palomo

a1 == a2.toString();

stringify (100%) por radtek

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

Nota los ejemplos siguientes asumen que las matrices son matrices unidimensionales ordenadas. .length comparación se ha eliminado para un punto de referencia común (agregar a1.length === a2.length a cualquiera de las sugerencias y obtendrá un aumento de rendimiento del ~ 10%). Elija las soluciones que mejor funcionen para usted, conociendo la velocidad y la limitación de cada una.

Nota no relacionada: es interesante ver a la gente recibiendo a John Waynes con todos los disparadores en el botón de voto negativo en respuestas perfectamente legítimas a esta pregunta.


24
2018-02-12 09:48



En la misma línea que JSON.encode es usar join ().

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;


    //slice so we do not effect the original
    //sort makes sure they are in order
    //join makes it a string so we can do a string compare
    var cA = arrA.slice().sort().join(","); 
    var cB = arrB.slice().sort().join(",");

    return cA===cB;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];  //will return true

console.log( checkArrays(a,b) );  //true
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //true

El único problema es si le importan los tipos que las últimas pruebas de comparación. Si te importan los tipos, deberás hacer un bucle.

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;

    //slice so we do not effect the orginal
    //sort makes sure they are in order
    var cA = arrA.slice().sort(); 
    var cB = arrB.slice().sort();

    for(var i=0;i<cA.length;i++){
         if(cA[i]!==cB[i]) return false;
    }

    return true;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];

console.log( checkArrays(a,b) );  //true
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //false

Si la orden debe seguir siendo la misma, que solo es un bucle, no se necesita ordenación.

function checkArrays( arrA, arrB ){

    //check if lengths are different
    if(arrA.length !== arrB.length) return false;


    for(var i=0;i<arrA.length;i++){
         if(arrA[i]!==arrB[i]) return false;
    }

    return true;

}

var a = [1,2,3,4,5];
var b = [5,4,3,2,1];
var c = [1,2,3,4];
var d = [1,2,3,4,6];
var e = ["1","2","3","4","5"];

console.log( checkArrays(a,a) );  //true
console.log( checkArrays(a,b) );  //false
console.log( checkArrays(a,c) );  //false
console.log( checkArrays(a,d) );  //false
console.log( checkArrays(a,e) );  //false

5
2017-10-20 14:30



para una matriz de una dimensión simplemente puede usar:

arr1.sort().toString() == arr2.sort().toString()

esto también se encargará de la matriz con índice no coincidente.


5
2017-09-25 09:28



Si está utilizando un marco de prueba como Moca con el Chai biblioteca de afirmaciones, puede usar profundo igualdad para comparar matrices.

expect(a1).to.deep.equal(a2)

Esto debería volver verdadero solo si las matrices tienen elementos iguales en los índices correspondientes.


5
2018-06-10 08:42