Vergleichen von zwei Arrays, um zu sehen, ob sie Objekte mit unterschiedlichen IDs enthalten

114716
Domenic

Das fühlt sich an wie ein Problem, das eine äußerst elegante Lösung hat. Aber mein Code ist mechanisch und unkompliziert. Nicht schlecht, aber ich bin interessiert, wenn es etwas Schöneres gibt.

Leistung ist nicht wirklich ein Problem. Ich suche elegant / kürzer, nicht schneller.

Bitte keine Underscore.js; Es lohnt sich nicht, mit meinen Kollegen zu kämpfen, um eine andere Bibliothek von Drittanbietern zu installieren, vor allem, wenn sie so viel ES5 dupliziert. Apropos: Wir arbeiten in einer reinen ES5-Umgebung, nutzen also alle Array-Extras, die Sie wünschen.

function getIds(array) {
    // We have added `Array.prototype.unique`; it does what you'd expect.
    return array.map(function (x) { return x.id; }).unique().sort();
}

function areDifferentByIds(a, b) {
    var idsA = getIds(a);
    var idsB = getIds(b);

    if (idsA.length !== idsB.length) {
        return true;
    }

    for (var i = 0; i < idsA.length; ++i) {
        if (idsA[i] !== idsB[i]) {
            return true;
        }
    }

    return false;
}
Antworten
10
Im Allgemeinen möchten Sie den Namen "containSameIds" nennen und einfach "return true" mit "return false" austauschen. Ich persönlich würde Arrays in Hash-Sets konvertieren und dann über eines und dann über ein anderes iterieren (wenn die Größen gleich sind). Ich würde nicht versuchen, die Funktion auf Kosten der Lesbarkeit zu kurz zu machen. Leonid vor 7 Jahren 0
Ich würde den Längenvergleichscode entfernen. Die for-Schleife darunter würde dasselbe Ergebnis ergeben. Der einzige Grund, um es dort hinzustellen, wäre Leistung. Gerben vor 7 Jahren 0
@Gerben betrachte `idsA = [" x "]`, `idsB = [" x "," y "]`. Domenic vor 7 Jahren 2
Fügen Sie auch Array.prototype.compare hinzu. :) Quentin Pradet vor 7 Jahren 0
@Domenic ich sehe. In diesem Fall sollte `! ==` `<` :-P sein Gerben vor 7 Jahren 1

4 Antworten auf die Frage

12
Gerben
function areDifferentByIds(a, b) {
    var idsA = a.map( function(x){ return x.id; } ).unique().sort();
    var idsB = b.map( function(x){ return x.id; } ).unique().sort();
    return (idsA.join(',') !== idsB.join(',') );
}

Dies ist die einfachste, die ich schaffen könnte. Ich denke, solche kleinen Funktionen müssen so in sich abgeschlossen sein wie möglich und brauchen keine anderen Funktionen. Dies macht sie lesbarer, wartbarer und natürlich wiederverwendbar. Ich habe deshalb die getIds-Funktion entfernt. Wenn Sie das anderswo verwenden, können Sie es behalten.

Sie könnten die gespeichert werden sollen function (x) { return x.id; }innerhalb einer Variablen, und wieder verwenden es auf beiden maps

Ich denke auch, dass es schneller ist, weil Join ziemlich schnell ist und keine for-Schleife benötigt.

BEARBEITEN Sie meinen nächsten Versuch

function areDifferentByIds(a, b) {
    var idsA = a.map( function(x){ return x.id; } ).unique();
    var idsB = b.map( function(x){ return x.id; } ).unique();
    var idsAB = a.concat(b).unique();
    return idsAB.length!==idsA.length
}

Zweitens können Sie es allgemeiner machen, indem Sie die zu prüfende Eigenschaft zu den Argumenten der Funktion hinzufügen. So etwas wie:

function areDifferentByProperty(a, b, prop) {
    var idsA = a.map( function(x){ return x[prop]; } ).unique();
    var idsB = b.map( function(x){ return x[prop]; } ).unique();
    var idsAB = a.concat(b).unique();
    return idsAB.length!==idsA.length
}

Drittens würde ich es viel lieber als EqualBy___ nennen. Scheint mir intuitiver, aber das ist nur ich.

Nein, meine IDs können beliebige Zeichenfolgen sein. idsA = ["x, y"] `vs idsB = [" x "," y "]` bricht es ab. Domenic vor 7 Jahren 0
Und ich sage jetzt, alle Lösungen, die auf der Auswahl einer "nicht erlernbaren" Folge von Unicode-Zeichen als Schreiner beruhen, werden wahrscheinlich in der realen Welt funktionieren, sind aber noch weniger elegant als mein Original;) Domenic vor 7 Jahren 1
Ich denke, weniger Code ist eleganter, aber ich denke, Sie haben Recht. Wenn die IDs beliebige Zeichenfolgen sein können, kann dies zu falschen Ergebnissen führen, was, gelinde gesagt, nicht sehr elegant ist. Gerben vor 7 Jahren 0
Ich habe einen weiteren Versuch unternommen, der unabhängig vom ID-Format funktionieren sollte. Siehe oben. Gerben vor 7 Jahren 0
OK, der zweite Versuch ist wirklich klug und entspricht genau dem, wonach ich suche. Wir haben einen Sieger!!! Domenic vor 7 Jahren 1
3
Quentin Pradet

Ich glaube nicht, dass Ihre Hauptlogik geändert werden kann, aber Sie können dies noch präziser schreiben:

function areDifferentByIds(a, b) {
    var idsA = getIds(a);
    var idsB = getIds(b);

    return idsA.length == idsB.length
      && idsA.every(function(e,i) { return idsB.indexOf(e) == i; });
}

Beachten Sie, dass alle und nur ab IE9 funktionieren. Auf der Seite Array.every des MDN wird eine Implementierung vorgeschlagen . Es ist aber immer noch ES5.


(Damit die folgenden Kommentare Sinn ergeben, habe ich den Unsinn weiter unten aufbewahrt).

Kennen Sie die ES5- reduce()Funktion? Sie durchläuft jeden Wert des Arrays ( xin meinem Code), teilt Ihnen den aktuellen Index ( iin meinem Code) mit und fordert Sie auf, Ihr Array mithilfe eines Akkumulators ( accin meinem Code) zu "reduzieren" .

Der Akkumulator wird zuerst auf gesetzt trueund ist nur dann wahr, wenn die aktuellen Indizes gleich sind.

function areDifferentByIds(a, b) {
    return a.reduce(function(acc,x,i) { return acc && a[i] == b[i] }, true);
}

Hier ist ein einfacheres Beispiel für die Reduzierung, das Ihnen helfen könnte zu verstehen, wie es funktioniert. Dieses fasst die Elemente meines Arrays zusammen:

sum = [1, 2, 3, 4].reduce(function(acc, x) { return acc + x}, 0);

Update :

  1. Diese neue Version vergleicht die ID-Eigenschaften. Ich glaube nicht, dass Sie den getIds()Schritt sowieso vermeiden können, weshalb er in der vorherigen Version nicht vorhanden war.
  2. b länger zu sein als a war kein Problem. b kürzer sein als a könnte eins gewesen sein: mit a = [undefined], a[20] == a[0]ist wahr. Die indexOfSuche nach Elementen bedeutet, dass ich mich damit nicht mehr beschäftigen muss.
  3. Es gibt keine Duplizierung, da IDs eindeutig sind.
  4. Ich wechselte zu every(), was dasselbe tut wie meine vorherige reduce()Implementierung, aber es ist viel einfacher.

Der Code:

function areDifferentByIds(a, b) {
    var idsA = getIds(a);
    var idsB = getIds(b);

    return a.every(function(e, i) { return b.indexOf(e) == i }
}

Habe ich noch etwas verpasst? edit : OK, ["x"] versus ["x", "y"] funktioniert nicht. Ich arbeite wieder.

OK, das ist schön: aus dem Kasten heraus zu denken. Aber ein paar Probleme: 1) vergleicht keine "id" -Eigenschaften; 2) wird nicht berücksichtigt, wenn "b" länger als "a" ist; 3) behandelt keine Out-of-Order-Werte oder verdoppelte Werte. Es sieht also so aus, als wäre es nur ein Ersatz für die "for" -Schleife in meiner ursprünglichen Lösung und nicht für den gesamten Code. Domenic vor 7 Jahren 0
Ihr Code ist der einfachste Weg, dies zu tun. Nur mit ES5 glaube ich nicht, dass Sie eine völlig andere Möglichkeit haben, dies zu tun. Alle anderen Lösungen verringern lediglich die Anforderungen an die von Ihnen verwendeten Prüfungen und expliziten Schleifen. Quentin Pradet vor 7 Jahren 0
1
Ross Patterson

Abhängig von der Leistung Ihrer JavaScript-Engine sort()kann Ihr Sort-Both-Then-Compare-Algorithmus sehr wahrscheinlich überfordert sein. Bei einigen Sorten ist die Leistung von O (n²) im schlechtesten Fall, die Sie schlagen könnten, indem Sie die beiden Sortierungen überspringen und alle Elemente von B in A durchsuchen. Beispiel:

function areDifferentByIds(a, b) {
    if (a.length !== b.length) {
        return true;
    }

    aLoop: for (var i = 0; i < a.length; ++i) {
        bLoop: for (var j = 0; j < b.length; ++i) {
            if (a[i] == b[j]) {
                continue aLoop;
            }
        }
        return true;
    }

    return false;
}
Ich kann nicht verstehen, warum jemand eine O (n²) Sortierung implementieren würde? Quentin Pradet vor 7 Jahren 0
Bei Ihrer Lösung geht es auch um die Leistung, nicht aber um prägnanten Code, den das OP nicht fragt. Es ist immer noch ein schöner Trick, danke fürs Teilen. Quentin Pradet vor 7 Jahren 0
Wenn `a`` [{x: 1} 'ist, {x: 1}] `und` b` `[{x: 1}]`, würde Ihr Code sagen, dass sie unterschiedlich sind. Ich wusste nicht, dass Sie Schleifen beschriften können. Gerben vor 7 Jahren 0
+1 für die Loop-Label-Neuheit, aber nicht wirklich viel besser als das, was ich bereits habe, wenn man die Leistung in Betracht zieht. Domenic vor 7 Jahren 0
1
Neil

Darf ich meine eigene Lösung vorschlagen? Die Eleganz liegt darin, wie es heißt. Zwei Objekte werden mit der Eigenschaft 'id' verglichen, wenn kein anderer Parameter übergeben wird. Andernfalls können Sie den Aufruf auf areDifferentByIds erweitern, um die zu vergleichenden Eigenschaften anzugeben.

function areDifferentByIds(a, b) {
    var addArgs = Array.prototype.slice.call(arguments, 2);
    var props = addArgs.length > 0 ? addArgs : ['id'];
    for(var diff=false, i=0; !diff && i<addArgs.length; i++) {
        diff = a[addArgs[i]] !== b[addArgs[i]];
    }
    return diff;
}

Anwendungsbeispiel:

// false
areDifferentByIds({id: 'test', b: 'me'}, {id: 'test', b:'test', c: 'test'});  
// false
areDifferentByIds({id: 'test', b: 'me'}, {id: 'test', b:'test', c: 'test'}, 'id');
// true 
areDifferentByIds({id: 'test', b: 'me'}, {id: 'test', b:'test', c: 'test'}, 'b'); 
// true
areDifferentByIds({id: 'test', b: 'me'}, {id: 'test', b:'test', c: 'test'}, 'id', 'b'); 

areDifferentByIds Lass es mich wissen. Ich hoffe das hilft!