Wie vergleicht man Arrays in JavaScript?



Answers

Während dies nur für skalare Arrays funktioniert, ist es kurz und bündig:

a1.length==a2.length && a1.every(function(v,i) { return v === a2[i]})

oder in ECMAScript 6 / CoffeeScript / TypeScript mit Arrow-Funktionen:

a1.length==a2.length && a1.every((v,i)=> v === a2[i])
Question

Ich möchte zwei Arrays vergleichen ... im Idealfall effizient. Nichts Besonderes, nur true wenn sie identisch sind, und false wenn nicht. Es überrascht nicht, dass der Vergleichsoperator nicht zu funktionieren scheint.

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

JSON-Codierung für jedes Array, aber gibt es eine schnellere oder "bessere" Möglichkeit, Arrays einfach zu vergleichen, ohne jeden Wert durchlaufen zu müssen?




Der praktische Weg

Ich denke, es ist falsch zu sagen, dass eine bestimmte Implementierung "The Right Way ™" ist, wenn sie nur "richtig" ("richtig") im Gegensatz zu einer "falschen" Lösung ist. Tomášs Lösung ist eine deutliche Verbesserung gegenüber einem stringbasierten Array-Vergleich, aber das bedeutet nicht, dass sie objektiv "richtig" ist. Was ist überhaupt richtig ? Ist es das schnellste? Ist es das flexibelste? Ist es am einfachsten zu verstehen? Ist es am schnellsten zu debuggen? Verwendet es die wenigsten Operationen? Hat es irgendwelche Nebenwirkungen? Keine einzige Lösung kann das Beste aus all den Dingen haben.

Tomáš könnte sagen, dass seine Lösung schnell ist, aber ich würde auch sagen, dass es unnötig kompliziert ist. Es versucht eine All-in-One-Lösung zu sein, die für alle Arrays funktioniert, verschachtelt oder nicht. Tatsächlich akzeptiert es sogar mehr als nur Arrays als Eingabe und versucht immer noch, eine "gültige" Antwort zu geben.

Generika bieten Wiederverwendbarkeit

Meine Antwort wird das Problem anders angehen. Ich beginne mit einer generischen arrayCompare Prozedur, die nur das Durchlaufen der Arrays betrifft. Von dort werden wir unsere anderen grundlegenden Vergleichsfunktionen wie arrayEqual und arrayDeepEqual usw

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

Meiner Meinung nach benötigt die beste Art von Code nicht einmal Kommentare, und dies ist keine Ausnahme. Hier passiert so wenig, dass Sie das Verhalten dieses Verfahrens fast ohne Aufwand verstehen können. Sicher, einige der ES6-Syntax scheinen Ihnen jetzt fremd zu sein, aber das liegt nur daran, dass ES6 relativ neu ist.

Wie der Typ andeutet, verwendet arrayCompare die Vergleichsfunktion f und zwei Eingabearrays xs und ys . In den meisten Fällen rufen wir nur f (x) (y) für jedes Element in den Eingabearrays auf. Wir geben ein frühes false wenn das benutzerdefinierte f false zurückgibt - dank der && Kurzschlussauswertung. Also ja, das bedeutet, dass der Komparator die Iteration früh stoppen kann und das Durchlaufen des Rests des Eingabe-Arrays verhindern kann, wenn dies nicht notwendig ist.

Strenger Vergleich

Mit unserer arrayCompare Funktion können wir einfach weitere Funktionen erstellen, die wir benötigen. Wir beginnen mit dem elementaren 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

So einfach ist das. arrayEqual kann mit arrayCompare und einer Komparatorfunktion definiert werden, die a mit Hilfe von === (für strikte Gleichheit) vergleicht.

Beachten Sie, dass wir auch equal als eigene Funktion definieren. Dies hebt die Rolle von arrayCompare als eine Funktion höherer Ordnung hervor, um unseren Komparator erster Ordnung in dem Kontext eines anderen Datentyps (Array) zu verwenden.

Loser Vergleich

Wir könnten arrayLooseEqual genauso einfach mit a == definieren. Wenn man jetzt 1 (Zahl) mit '1' (String) vergleicht, wird das Ergebnis 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

Tiefer Vergleich (rekursiv)

Sie haben wahrscheinlich bemerkt, dass dies nur oberflächlicher Vergleich ist. Tomášs Lösung ist sicher "The Right Way ™", weil es implizit einen tiefen Vergleich macht, oder?

Nun, unser arrayCompare Verfahren ist vielseitig genug, um einen tiefen Gleichheitstest zu einem Kinderspiel zu machen ...

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

So einfach ist das. Wir bauen einen tiefen Komparator mit einer anderen Funktion höherer Ordnung. Dieses Mal wickeln wir arrayCompare mit einem benutzerdefinierten Komparator ab, der überprüft, ob a und b Arrays sind. Wenn dies der arrayDeepCompare , arrayDeepCompare erneut arrayDeepCompare andernfalls vergleichen Sie a und b mit dem benutzerdefinierten Komparator ( f ). Dies ermöglicht uns, das tiefe Vergleichsverhalten getrennt von dem tatsächlichen Vergleich der einzelnen Elemente zu halten. Dh, wie das obige Beispiel zeigt, können wir tief vergleichen, indem wir equal , looseEqual oder irgendeinen anderen Komparator verwenden, den wir machen.

Da arrayDeepCompare ist, können wir es teilweise wie in den vorherigen Beispielen anwenden

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

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

Für mich ist das bereits eine deutliche Verbesserung gegenüber Tomášs Lösung, da ich je nach Bedarf explizit einen flachen oder tiefen Vergleich für meine Arrays wählen kann.

Objektvergleich (Beispiel)

Was nun, wenn Sie eine Reihe von Objekten oder etwas haben? Vielleicht möchten Sie diese Arrays als "gleich" betrachten, wenn jedes Objekt denselben id Wert hat ...

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

So einfach ist das. Hier habe ich Vanilla JS-Objekte verwendet, aber diese Art von Komparator könnte für jeden Objekttyp funktionieren; sogar Ihre benutzerdefinierten Objekte. Tomáš 'Lösung müsste komplett überarbeitet werden, um diese Art von Gleichheitstest zu unterstützen

Deep Array mit Objekten? Kein Problem. Wir haben sehr vielseitige, generische Funktionen entwickelt, so dass sie in einer Vielzahl von Anwendungsfällen funktionieren.

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

Willkürlicher Vergleich (Beispiel)

Oder was, wenn Sie einen völlig willkürlichen Vergleich durchführen wollten? Vielleicht möchte ich wissen, ob jedes x größer ist als jedes 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

Weniger ist mehr

Sie können sehen, dass wir mehr mit weniger Code machen. Es gibt nichts Kompliziertes an arrayCompare selbst und jeder der benutzerdefinierten Vergleicher, die wir erstellt haben, hat eine sehr einfache Implementierung.

Mit Leichtigkeit können wir genau definieren, wie wir zwei Arrays vergleichen wollen - flach, tief, streng, lose, einige Objekteigenschaften, oder irgendeine willkürliche Berechnung oder irgendeine Kombination von diesen - alles unter Verwendung einer Prozedur , arrayCompare . Vielleicht sogar einen RegExp Komparator RegExp ! Ich weiß, wie Kinder diese Regungen lieben ...

Ist es das schnellste? Nee. Aber es muss wahrscheinlich auch nicht sein. Wenn Geschwindigkeit die einzige Metrik ist, die verwendet wird, um die Qualität unseres Codes zu messen, würde eine Menge wirklich toller Code weggeworfen werden. Deshalb nenne ich diesen Ansatz den praktischen Weg . Oder vielleicht, um fairer zu sein, ein praktischer Weg. Diese Beschreibung ist für diese Antwort geeignet, weil ich nicht sage, dass diese Antwort nur im Vergleich zu einer anderen Antwort praktisch ist; es ist objektiv wahr. Wir haben ein hohes Maß an Praktikabilität erreicht mit sehr wenig Code, der sehr einfach zu verstehen ist. Kein anderer Code kann sagen, dass wir diese Beschreibung nicht verdient haben.

Ist das die richtige Lösung für Sie? Das ist für dich zu entscheiden. Und niemand anderes kann das für dich tun; nur du weißt, was deine Bedürfnisse sind. In fast allen Fällen schätze ich einfachen, praktischen und vielseitigen Code gegenüber kluger und schneller Art. Was Sie schätzen, kann abweichen, also wählen Sie, was für Sie funktioniert.

Bearbeiten

Meine alte Antwort konzentrierte sich mehr darauf, arrayEqual in winzige Prozeduren zu zerlegen. Es ist eine interessante Übung, aber nicht wirklich die beste (praktischste) Art, dieses Problem anzugehen. Wenn Sie interessiert sind, können Sie diesen Überarbeitungsverlauf sehen.




function compareArrays(arrayA, arrayB) {
    if (arrayA.length != arrayB.length) return true;
    for (i = 0; i < arrayA.length; i++)
        if (arrayB.indexOf(arrayA[i]) == -1) {
            return true;
        }
    }
    for (i = 0; i < arrayB.length; i++) {
        if (arrayA.indexOf(arrayB[i]) == -1) {
            return true;
        }
    }
    return false;
}



We could do this the functional way, using every ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/every )

function compareArrays(array1, array2) {
    if (array1.length === array2.length)
        return array1.every((a, index) => a === array2[index])
    else
        return false
}

// test
var a1 = [1,2,3];
var a2 = [1,2,3];

var a3 = ['a', 'r', 'r', 'a', 'y', '1']
var a4 = ['a', 'r', 'r', 'a', 'y', '2']

console.log(compareArrays(a1,a2)) // true
console.log(compareArrays(a1,a3)) // false
console.log(compareArrays(a3,a4)) // false



Im Geiste der ursprünglichen Frage:

Ich möchte zwei Arrays vergleichen ... im Idealfall effizient . Nichts Besonderes , nur wahr, wenn sie identisch sind, und falsch, wenn nicht.

Ich habe Leistungstests für einige der hier vorgeschlagenen einfachen Vorschläge mit den folgenden results (schnell bis langsam) durchgeführt:

while (67%) von Tim Down

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

every (69%) von user2782196

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

reduce (74%) durch DEI

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

join & toString (78%) von Gaizka Allende & Vivek

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

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

Half toString (90%) von Victor Palomo

a1 == a2.toString();

stringify (100%) von radtek

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

Beachten Sie, dass die folgenden Beispiele davon ausgehen, dass die Arrays sortierte, eindimensionale Arrays sind. .length Vergleich wurde für einen gemeinsamen Benchmark entfernt (addiere a1.length === a2.length zu irgendeinem der Vorschläge und du a1.length === a2.length eine ~ 10% Leistungssteigerung). Wählen Sie die Lösungen, die am besten für Sie geeignet sind, und kennen Sie die Geschwindigkeit und die Einschränkungen der einzelnen Lösungen.

Unverwandter Hinweis: Es ist interessant zu sehen, dass Leute alle auslöserfreudigen John Waynes auf der unteren Abstimmungsschaltfläche auf vollkommen legitime Antworten auf diese Frage bekommen.




Extending Tomáš Zato idea. Tomas's Array.prototype.compare should be infact called Array.prototype.compareIdentical.

It passes on:

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

But fails on:

[[1, 2, [3, 2]],1, 2, [3, 2]].compareIdentical([1, 2, [3, 2],[1, 2, [3, 2]]])

Here is better (in my opinion) version:

Array.prototype.compare = 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;

    this.sort();
    array.sort();
    for (var i = 0; i < this.length; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].compare(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;
}

http://jsfiddle.net/igos/bcfCY/




This function compares two arrays of arbitrary shape and dimesionality:

function equals(a1, a2) {

    if (!Array.isArray(a1) || !Array.isArray(a2)) {
        throw new Error("Arguments to function equals(a1, a2) must be arrays.");
    }

    if (a1.length !== a2.length) {
        return false;
    }

    for (var i=0; i<a1.length; i++) {
        if (Array.isArray(a1[i]) && Array.isArray(a2[i])) {
            if (equals(a1[i], a2[i])) {
                continue;
            } else {
                return false;
            }
        } else {
            if (a1[i] !== a2[i]) {
                return false;
            }
        }
    }

    return true;
}



var a1 = [1,2,3,6];
var a2 = [1,2,3,5];

function check(a, b) {
  return (a.length != b.length) ? false : 
  a.every(function(row, index) {
    return a[index] == b[index];
  });
}  

check(a1, a2);

////// OR ///////

var a1 = [1,2,3,6];
var a2 = [1,2,3,6];

function check(a, b) {
  return (a.length != b.length) ? false : 
  !(a.some(function(row, index) {
    return a[index] != b[index];
  }));
}  

check(a1, a2)



While the top answer to this question is correct and good, the code provided could use some improvement.

Below is my own code for comparing arrays and objects. The code is short and simple:

Array.prototype.equals = function(otherArray) {
  if (!otherArray || this.length != otherArray.length) return false;
  return this.reduce(function(equal, item, index) {
    var otherItem = otherArray[index];
    var itemType = typeof item, otherItemType = typeof otherItem;
    if (itemType !== otherItemType) return false;
    return equal && (itemType === "object" ? item.equals(otherItem) : item === otherItem);
  }, true);
};

if(!Object.prototype.keys) {
  Object.prototype.keys = function() {
    var a = [];
    for (var key in this) {
      if (this.hasOwnProperty(key)) a.push(key);
    }
    return a;
  }
  Object.defineProperty(Object.prototype, "keys", {enumerable: false});
}

Object.prototype.equals = function(otherObject) {
  if (!otherObject) return false;
  var object = this, objectKeys = object.keys();
  if (!objectKeys.equals(otherObject.keys())) return false;
  return objectKeys.reduce(function(equal, key) {
    var value = object[key], otherValue = otherObject[key];
    var valueType = typeof value, otherValueType = typeof otherValue;
    if (valueType !== otherValueType) return false;
    // this will call Array.prototype.equals for arrays and Object.prototype.equals for objects
    return equal && (valueType === "object" ? value.equals(otherValue) : value === otherValue);
  }, true);
}
Object.defineProperty(Object.prototype, "equals", {enumerable: false});

This code supports arrays nested in objects and objects nested in arrays.

You can see a full suite of tests and test the code yourself at this repl: https://repl.it/Esfz/3




Here's a CoffeeScript version, for those who prefer that:

Array.prototype.equals = (array) ->
  return false if not array # if the other array is a falsy value, return
  return false if @length isnt array.length # compare lengths - can save a lot of time

  for item, index in @
    if item instanceof Array and array[index] instanceof Array # Check if we have nested arrays
      if not item.equals(array[index]) # recurse into the nested arrays
        return false
    else if this[index] != array[index]
      return false # Warning - two different object instances will never be equal: {x:20} != {x:20}
  true

All credits goes to @tomas-zato.




Es ist unklar, was du mit "identisch" meinst. Sind beispielsweise die Arrays a und b identisch (beachten Sie die verschachtelten Arrays)?

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

Hier ist eine optimierte Array-Vergleichsfunktion, die entsprechende Elemente jedes Arrays der Reihe nach strikt nach Gleichheit vergleicht und keinen rekursiven Vergleich von Array-Elementen durchführt, die selbst Arrays sind, was bedeutet, dass für das obige Beispiel arraysIdentical(a, b) false arraysIdentical(a, b) . Es funktioniert im allgemeinen Fall, die JSON- und join() -basierte Lösungen nicht:

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



If they are two arrays of numbers or strings only, this is a quick one-line one

const array1 = [1, 2, 3];
const array2 = [1, 3, 4];
console.log(array1.join(',') === array2.join(',')) //false

const array3 = [1, 2, 3];
const array4 = [1, 2, 3];
console.log(array3.join(',') === array4.join(',')) //true



Auch wenn es viele Antworten gibt, von denen ich glaube, dass sie hilfreich sind:

const newArray = [ ...new Set( [...arr1, ...arr2] ) ]

Es wird nicht in der Frage angegeben, wie die Struktur des Arrays aussehen wird. Wenn Sie also sicher wissen, dass Sie keine verschachtelten Arrays oder Objekte in Ihrem Array haben werden (das ist mir passiert, deshalb bin ich dazu gekommen) antwort) der obige Code wird funktionieren.

Was passiert, ist, dass wir den Spread-Operator (...) verwenden, um beide Arrays zu verbinden, dann verwenden wir Set, um alle Duplikate zu eliminieren. Sobald Sie haben, dass Sie ihre Größen vergleichen können, wenn alle drei Reihen die gleiche Größe haben, sind Sie gut zu gehen.

Diese Antwort ignoriert auch die Reihenfolge der Elemente , wie gesagt, die genaue Situation ist mir passiert, also könnte vielleicht jemand in der gleichen Situation hier enden (so wie ich).

Bearbeiten1.

Antwort von Dmitry Grinko's Frage: "Warum hast du den Spread Operator (...) hier benutzt - ... neues Set? Es funktioniert nicht"

Betrachten Sie diesen Code:

const arr1 = [ 'a', 'b' ]
const arr2 = [ 'a', 'b', 'c' ]
const newArray = [ new Set( [...arr1, ...arr2] ) ]
console.log(newArray)

Du wirst kriegen

[ Set { 'a', 'b', 'c' } ]

In order to work with that value you'd need to use some Set properties (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set ). On the other hand, when you use this code:

const arr1 = [ 'a', 'b' ]
const arr2 = [ 'a', 'b', 'c' ]
const newArray = [ ...new Set( [...arr1, ...arr2] ) ]
console.log(newArray)

You'll get

[ 'a', 'b', 'c' ]

That's the difference, the former would give me a Set, it would work too as I could get the size of that Set, but the latter gives me the array I need, what's more direct to the resolution.




JSON.stringify(collectionNames).includes(JSON.stringify(sourceNames)) ?  array.push(collection[i]) : null

This is how i did it.




für Single-Dimension-Array können Sie einfach verwenden:

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

Dies wird auch für Array mit nicht übereinstimmendem Index sorgen.




Links