Jak uzyskać różnicę między dwiema tablicami w JavaScript?

751

Czy istnieje sposób na zwrócenie różnicy między dwiema tablicami w JavaScript?

Na przykład:

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

// need ["c", "d"]
John Adawan
źródło
9
Symetryczny czy niesymetryczny?
Wyścigi lekkości na orbicie
Dzięki nowej funkcji ES6 można to zrobić jako prostą linijkę (użycie jej we wszystkich głównych przeglądarkach zajmie dużo czasu). W każdym razie sprawdź moją odpowiedź
Salvador Dali
1
ważnym aspektem rozwiązania jest wydajność. asymptotyczna złożoność czasowa tego rodzaju operacji - w innych językach - jest O(a1.length x log(a2.length))- czy ta wydajność jest możliwa w JavaScript?
Raul,

Odpowiedzi:

218

Zakładam, że porównujesz normalną tablicę. Jeśli nie, musisz zmienić pętlę for na pętlę for .. in .

function arr_diff (a1, a2) {

    var a = [], diff = [];

    for (var i = 0; i < a1.length; i++) {
        a[a1[i]] = true;
    }

    for (var i = 0; i < a2.length; i++) {
        if (a[a2[i]]) {
            delete a[a2[i]];
        } else {
            a[a2[i]] = true;
        }
    }

    for (var k in a) {
        diff.push(k);
    }

    return diff;
}

console.log(arr_diff(['a', 'b'], ['a', 'b', 'c', 'd']));
console.log(arr_diff("abcd", "abcde"));
console.log(arr_diff("zxc", "zxc"));

Lepszym rozwiązaniem, jeśli nie zależy ci na kompatybilności wstecznej, jest użycie filtra. Ale nadal to rozwiązanie działa.

Myśliciel
źródło
46
Może to działać, ale wykonuje trzy pętle, aby osiągnąć to, co można zrobić w jednym wierszu kodu przy użyciu metody filtrującej Array.
Joshaven Potter
9
Wystarczy być jasne, to realizuje symetryczny difference of a1 i a2 , w przeciwieństwie do innych odpowiedzi zamieszczone tutaj.
200_sukces
25
To nie jest najlepsza odpowiedź, ale udzielam jej charytatywnej opinii, aby zrekompensować nieuczciwe opinie. Należy zlekceważyć tylko błędne odpowiedzi, a gdybym pracował nad projektem z przeglądarkami cruft w zasięgu (zdarzają się trudne czasy), ta odpowiedź może nawet być pomocna.
Michael Scheper
3
Mogę wiedzieć, co się stanie, gdy var a1 = ['a', 'b'];i var a2 = ['a', 'b', 'c', 'd', 'b'];, zwróci błędną odpowiedź , to znaczy ['c', 'd', 'b']zamiast ['c', 'd'].
skbly7,
4
Najszybszy sposób to najbardziej naiwne rozwiązanie. Przetestowałem wszystkie proponowane rozwiązania dla różnic symetrycznych w tym wątku, a zwycięzcą jest:function diff2(a, b) { var i, la = a.length, lb = b.length, res = []; if (!la) return b; else if (!lb) return a; for (i = 0; i < la; i++) { if (b.indexOf(a[i]) === -1) res.push(a[i]); } for (i = 0; i < lb; i++) { if (a.indexOf(b[i]) === -1) res.push(b[i]); } return res; }
Nomaed
1223

Istnieje lepszy sposób korzystania z ES7:


Skrzyżowanie

 let intersection = arr1.filter(x => arr2.includes(x));

Różnica przecięcia Diagram Venna

Bo [1,2,3] [2,3]się podda[2,3] . Z drugiej strony, bo [1,2,3] [2,3,5]zwróci to samo.


Różnica

let difference = arr1.filter(x => !arr2.includes(x));

Właściwa różnica Diagram Venna

Dla [1,2,3] [2,3] się podda[1] . Z drugiej strony, bo [1,2,3] [2,3,5]zwróci to samo.


Aby uzyskać różnicę symetryczną , możesz:

let difference = arr1
                 .filter(x => !arr2.includes(x))
                 .concat(arr2.filter(x => !arr1.includes(x)));

Różnica symetryczna Diagram Venna

W ten sposób otrzymasz tablicę zawierającą wszystkie elementy arr1, których nie ma w arr2 i odwrotnie

Jak zauważył @Joshaven Potter w swojej odpowiedzi, możesz dodać to do Array.prototype, aby można go było użyć w następujący sposób:

Array.prototype.diff = function(arr2) { return this.filter(x => !arr2.includes(x)); }
[1, 2, 3].diff([2, 3])
Luis Sieira
źródło
3
Wolę sprawdzać < 0zamiast== -1
Vic
1
Obliczanie Arrayróżnicy jest tak zwane set operation, ponieważ wyszukiwanie właściwości jest zadaniem własnym Sets, które są o rząd wielkości szybciej niż indexOf/ includes. Mówiąc wprost, twoje rozwiązanie jest bardzo nieefektywne i raczej powolne.
@ftor, ale z Set, wartości muszą być unikalne, nie?
CervEd,
1
@LuisSieira Rozumiem, że to zadziałałoby, [1,2,3] [2,3,5]biorąc pod uwagę, że liczby są unikalne, ale gdybyś powiedział [1,1,2,3] [1,2,3,5]i spodziewał się [1], że nie będziesz mógł użyć Set. Twoje rozwiązanie też by nie działało: - / Skończyłem tworzyć tę funkcję, ponieważ nie mogłem znaleźć zadowalającego sposobu, aby zrobić to bardziej zwięźle. Jeśli masz jakieś pomysły, jak to zrobić, chciałbym wiedzieć!
CervEd,
3
Czy Array.includes()funkcja ES7 nie jest dostępna zamiast ES6? (1) (2) - i aby kontynuować, używając ES6 możesz użyć Array.some()np. let intersection = aArray.filter(a => bArray.some(b => a === b))Nie?
Jari Keinänen
910
Array.prototype.diff = function(a) {
    return this.filter(function(i) {return a.indexOf(i) < 0;});
};

////////////////////  
// Examples  
////////////////////

[1,2,3,4,5,6].diff( [3,4,5] );  
// => [1, 2, 6]

["test1", "test2","test3","test4","test5","test6"].diff(["test1","test2","test3","test4"]);  
// => ["test5", "test6"]

Uwaga indeks i filtr nie są dostępne np. Przed ie9.

Joshaven Potter
źródło
50
Jedyną istotną przeglądarką, która nie obsługuje filtrów i indexOf, jest IE8. IE9 obsługuje je oba. Więc to nie jest źle.
Bryan Larsen
14
ie7 i ie8 są nadal (niestety) bardzo istotne, jednak kod polifill dla obu funkcji można znaleźć na stronie MDN: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… developer.mozilla.org/en/JavaScript/ Odwołanie / Global_Objects /… Załaduj kod wymieniony w „kompatybilności” za pośrednictwem warunkowego IE & BOOM. Ie7 / 8 są obsługiwane.
1nfiniti
44
To rozwiązanie ma czas działania O (n ^ 2), rozwiązanie liniowe byłoby znacznie bardziej wydajne.
jholloman
75
Jeśli użyjesz takiej funkcji: [1,2,3].diff([3,4,5])wróci [1,2]zamiast, [1,2,4,5]więc nie rozwiąże problemu w pierwotnym pytaniu, o czym należy pamiętać.
Bugster
12
@AlinPurcaru Nieobsługiwane przez archaiczne przeglądarki! = Źle. Biorąc pod uwagę Netscape 2.0, większość kodu JS jest w tej definicji „niepoprawna”. To głupie powiedzieć.
NullUserException
304

Jest to zdecydowanie najłatwiejszy sposób na uzyskanie dokładnie tego, czego szukasz, za pomocą jQuery:

var diff = $(old_array).not(new_array).get();

diffteraz zawiera to, co było w old_arraytym, czego nie manew_array

superfoniczny
źródło
4
@Batman Tak, ale tylko wtedy, gdy odnoszą się do tego samego obiektu ( {a: 1} != {a: 1}) ( dowód )
Matmarbon,
Czy można go używać z tablicami przechowującymi dane obiektu niestandardowego? Próbowałem w rzeczywistości w ten sam sposób, ale to nie zadziałało. Wszelkie pomysły będą bardzo znaczące.
LetMeCodeY
8
Czy to podstęp? Dokument uważa ten sposób jako część elementu DOM Metod a nie jako ogólnym pomocnika tablicy. Więc może teraz działać w ten sposób, ale może nie w przyszłych wersjach, ponieważ nie było przeznaczone do użycia w ten sposób. Chociaż byłbym szczęśliwy, gdyby był to oficjalny pomocnik tablicy.
robsch
1
@robsch Kiedy używasz .notz tablicą, jQuery używa jej wbudowanego narzędzia, .grep()które jest specjalnie do filtrowania tablic. Nie widzę tego zmieniającego się.
superfoniczny
1
@vsync Wygląda na to, że masz symetryczną różnicę
superfoniczny
158

Metoda różnicy w Underscore (lub jej zastąpienie, Lo-Dash ) również może to zrobić:

(R)eturns the values from array that are not present in the other arrays

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]

Podobnie jak w przypadku każdej funkcji podkreślenia, możesz również używać jej w stylu bardziej obiektowym:

_([1, 2, 3, 4, 5]).difference([5, 2, 10]);
mahemoff
źródło
4
Myślę, że jest to dobre rozwiązanie pod względem wydajności, zwłaszcza, że ​​lodash i podkreślenie walczą o najlepsze wdrożenie. Ponadto jest kompatybilny z IE6.
mahemoff
4
Uwaga, ta implementacja nie będzie działać dla tablic obiektów. Aby uzyskać więcej informacji, zobacz stackoverflow.com/q/8672383/14731 .
Gili
1
Jak wspomina jedna z odpowiedzi, działa, jeśli jest to ten sam obiekt, ale nie, jeśli dwa obiekty mają te same właściwości. Myślę, że to w porządku, ponieważ pojęcia równości są różne (np. Może to być atrybut „id” w niektórych aplikacjach). Byłoby jednak dobrze, gdybyś mógł przejść test porównawczy do przecięcia ().
mahemoff
Dla potomności: Lodash ma teraz _.differenceBy (), która wykonuje wywołanie zwrotne w celu porównania; jeśli porównujesz obiekty, możesz dodać funkcję, która porównuje je w dowolny sposób.
SomeCallMeTim
2
Uważaj, jeśli kolejność argumentów zostanie odwrócona, to nie zadziała. na przykład. _.difference ([5, 2, 10], [1, 2, 3, 4, 5]); nie można uzyskać różnicy
Russj
79

Zwykły JavaScript

Istnieją dwie możliwe interpretacje „różnicy”. Pozwolę ci wybrać, który chcesz. Powiedz, że masz:

var a1 = ['a', 'b'     ];
var a2 = [     'b', 'c'];
  1. Jeśli chcesz uzyskać ['a'], użyj tej funkcji:

    function difference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      return result;
    }
  2. Jeśli chcesz dostać ['a', 'c'](wszystkie elementy zawarte w jednym z nich a1 lub w a2obu - tak zwana różnica symetryczna ), użyj tej funkcji:

    function symmetricDifference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      for (i = 0; i < a2.length; i++) {
        if (a1.indexOf(a2[i]) === -1) {
          result.push(a2[i]);
        }
      }
      return result;
    }

Lodash / Underscore

Jeśli używasz lodash, możesz użyć _.difference(a1, a2)(przypadek 1 powyżej) lub _.xor(a1, a2)(przypadek 2).

Jeśli używasz Underscore.js, możesz użyć _.difference(a1, a2) funkcji dla przypadku 1.

Zestaw ES6, do bardzo dużych tablic

Powyższy kod działa we wszystkich przeglądarkach. Jednak w przypadku dużych tablic składających się z ponad 10 000 elementów staje się dość wolny, ponieważ ma złożoność O (n²). W wielu nowoczesnych przeglądarkach możemy skorzystać z Setobiektu ES6, aby przyspieszyć działanie. Lodash automatycznie używa, Setgdy jest dostępny. Jeśli nie korzystasz z lodash, skorzystaj z następującej implementacji, zainspirowanej postem na blogu Axela Rauschmayera :

function difference(a1, a2) {
  var a2Set = new Set(a2);
  return a1.filter(function(x) { return !a2Set.has(x); });
}

function symmetricDifference(a1, a2) {
  return difference(a1, a2).concat(difference(a2, a1));
}

Notatki

Zachowanie wszystkich przykładów może być zaskakujące lub nieoczywiste, jeśli zależy Ci na -0, +0, NaN lub rzadkich tablicach. (W przypadku większości zastosowań nie ma to znaczenia).

Jo Liss
źródło
Podziękować. uratowałeś mi dzień Musiałem porównać tablice 300 000, a twoje rozwiązanie „Set” działało idealnie. To powinna być zaakceptowana odpowiedź.
justadev
52

Aby uzyskać różnicę symetryczną , musisz porównać tablice na dwa sposoby (lub na wszystkie sposoby w przypadku wielu tablic)

wprowadź opis zdjęcia tutaj


ES7 (ECMAScript 2016)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => !b.includes(x)),
        ...b.filter(x => !a.includes(x))
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => !unique.includes(x));
    }));
}

ES6 (ECMAScript 2015)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => b.indexOf(x) === -1),
        ...b.filter(x => a.indexOf(x) === -1)
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => unique.indexOf(x) === -1);
    }));
}

ES5 (ECMAScript 5.1)

// diff between just two arrays:
function arrayDiff(a, b) {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var other = i === 1 ? a : b;
        arr.forEach(function(x) {
            if (other.indexOf(x) === -1) {
                diff.push(x);
            }
        });
    })

    return diff;
}

// diff between multiple arrays:
function arrayDiff() {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var others = arrays.slice(0);
        others.splice(i, 1);
        var otherValues = Array.prototype.concat.apply([], others);
        var unique = otherValues.filter(function (x, j) { 
            return otherValues.indexOf(x) === j; 
        });
        diff = diff.concat(arr.filter(x => unique.indexOf(x) === -1));
    });
    return diff;
}

Przykład:

// diff between two arrays:
const a = ['a', 'd', 'e'];
const b = ['a', 'b', 'c', 'd'];
arrayDiff(a, b); // (3) ["e", "b", "c"]

// diff between multiple arrays
const a = ['b', 'c', 'd', 'e', 'g'];
const b = ['a', 'b'];
const c = ['a', 'e', 'f'];
arrayDiff(a, b, c); // (4) ["c", "d", "g", "f"]

Różnica między tablicami obiektów

function arrayDiffByKey(key, ...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter( x =>
            !unique.some(y => x[key] === y[key])
        );
    }));
}

Przykład:

const a = [{k:1}, {k:2}, {k:3}];
const b = [{k:1}, {k:4}, {k:5}, {k:6}];
const c = [{k:3}, {k:5}, {k:7}];
arrayDiffByKey('k', a, b, c); // (4) [{k:2}, {k:4}, {k:6}, {k:7}]
Luca Borrione
źródło
49

Czystsze podejście w ES6 to następujące rozwiązanie.

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

Różnica

a2.filter(d => !a1.includes(d)) // gives ["c", "d"]

Skrzyżowanie

a2.filter(d => a1.includes(d)) // gives ["a", "b"]

Unia dysjunkcyjna (różnica symetryczna)

[ ...a2.filter(d => !a1.includes(d)),
  ...a1.filter(d => !a2.includes(d)) ]
ifelse.codes
źródło
Działa tylko w jednym kierunku. Teraz wyobraź sobie, że a1 = ['a', 'b', 'e']: e nie zostanie wyodrębnione.
imrok
tak, tak działa różnica w teorii mnogości. (a2 -a1) to, czego szukasz, to (a2-a1) + (a1-a2)
ifelse.codes
1
@imrok Wierzę, że tego właśnie szukasz [... a2.filter (d =>! a1.includes (d)), ... (a1.filter (d =>! a2.includes (d)) )]
ifelse.codes
2
Piękne rozwiązanie, dzięki!
Thiago Alves,
40

W takim przypadku możesz użyć zestawu . Jest zoptymalizowany do tego rodzaju operacji (połączenie, przecięcie, różnica).

Upewnij się, że dotyczy twojej sprawy, gdy nie zezwala na powielanie.

var a = new JS.Set([1,2,3,4,5,6,7,8,9]);
var b = new JS.Set([2,4,6,8]);

a.difference(b)
// -> Set{1,3,5,7,9}
Samuel Carrijo
źródło
4
To wygląda na fajną bibliotekę! Jaka szkoda, że nie można pobrać tylko Setfunkcji bez konieczności dostać wszystko inne ...
Blixt
@Blixt Wierzę, że możesz to wszystko pobrać i dołączyć tylko plik set.js
Samuel Carrijo
Zestaw jest również implementowany w zamknięciu Google. closure-library.googlecode.com/svn/docs/…
Ben Flynn
32
function diff(a1, a2) {
  return a1.concat(a2).filter(function(val, index, arr){
    return arr.indexOf(val) === arr.lastIndexOf(val);
  });
}

Scal obie tablice, unikalne wartości pojawią się tylko raz, więc indexOf () będzie taki sam jak lastIndexOf ().

pdbrito
źródło
3
Zgadzam się, że jest to najczystszy i najprostszy sposób i miło, że nie wymaga dotykania prototypu. „Jeśli nie potrafisz wyjaśnić tego sześciolatkowi, sam tego nie rozumiesz.” - Albert Einstein
lacostenycoder
18

aby odjąć jedną tablicę od drugiej, po prostu użyj poniższego fragmentu:

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

var items = new Array();

items = jQuery.grep(a1,function (item) {
    return jQuery.inArray(item, a2) < 0;
});

Zwróci [„1,” 2 ”,„ 6 ”], które są elementami pierwszej tablicy, które nie istnieją w drugiej.

Dlatego, zgodnie z twoim przykładem problemu, dokładnym rozwiązaniem jest następujący kod:

var array1 = ["test1", "test2","test3", "test4"];
var array2 = ["test1", "test2","test3","test4", "test5", "test6"];

var _array = new Array();

_array = jQuery.grep(array2, function (item) {
     return jQuery.inArray(item, array1) < 0;
});
Glin___
źródło
14

Wraz z pojawieniem się ES6 z zestawami i operatorem splat (w chwili, gdy działa tylko w Firefoksie, sprawdź tabelę zgodności ), możesz napisać jedną linijkę:

var a = ['a', 'b', 'c', 'd'];
var b = ['a', 'b'];
var b1 = new Set(b);
var difference = [...new Set([...a].filter(x => !b1.has(x)))];

co spowoduje [ "c", "d" ].

Salvador Dali
źródło
Ciekawe, jak to się różni od robieniab.filter(x => !a.indexOf(x)))
chovy
1
@ chovy różni się złożonością czasową. Moim rozwiązaniem jest O(n + m)twoje rozwiązanie, O(n * m)gdzie n i m są długościami tablic. Weź długie listy, a moje rozwiązanie uruchomi się w kilka sekund, a twoje zajmie kilka godzin.
Salvador Dali,
Co powiesz na porównanie atrybutu listy obiektów? Czy to możliwe przy użyciu tego rozwiązania?
chovy
2
a.filter(x => !b1.has(x))jest prostsze. I pamiętać, spec wymaga tylko złożoność się n * f(m) + mz f(m)sublinear średnio. To lepsze niż n * m, ale niekoniecznie n + m.
Oriol
2
@SalvadorDali var difference = [...new Set([...a].filter(x => !b1.has(x)))];Dlaczego tworzysz zduplikowaną tablicę „a”? Dlaczego zamieniasz wynik filtra w zestaw, a następnie z powrotem w tablicę? Czy to nie jest równoważne zvar difference = a.filter(x => !b1.has(x));
Deepak Mittal
13

Funkcjonalne podejście z ES2015

Obliczanie differencemiędzy dwoma tablicami jest jedną z Setoperacji. Termin ten już wskazuje, że Setnależy użyć rodzimego typu, aby zwiększyć szybkość wyszukiwania. Tak czy inaczej, podczas obliczania różnicy między dwoma zestawami istnieją trzy kombinacje:

[+left difference] [-intersection] [-right difference]
[-left difference] [-intersection] [+right difference]
[+left difference] [-intersection] [+right difference]

Oto funkcjonalne rozwiązanie, które odzwierciedla te permutacje.

Po lewej difference:

// small, reusable auxiliary functions

const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// left difference

const differencel = xs => ys => {
  const zs = createSet(ys);
  return filter(x => zs.has(x)
     ? false
     : true
  ) (xs);
};


// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];


// run the computation

console.log( differencel(xs) (ys) );

Po prawej difference:

differencerjest banalny. To tylko differencelz odwróconymi argumentami. Możesz napisać funkcję dla wygody:const differencer = flip(differencel) . To wszystko!

Symetryczny difference:

Teraz, gdy mamy lewą i prawą, implementacja symetrycznej również differencestaje się trywialna:

// small, reusable auxiliary functions

const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const concat = y => xs => xs.concat(y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// left difference

const differencel = xs => ys => {
  const zs = createSet(ys);
  return filter(x => zs.has(x)
     ? false
     : true
  ) (xs);
};


// symmetric difference

const difference = ys => xs =>
 concat(differencel(xs) (ys)) (flip(differencel) (xs) (ys));

// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];


// run the computation

console.log( difference(xs) (ys) );

Myślę, że ten przykład jest dobrym punktem wyjścia do uzyskania wrażenia, co oznacza programowanie funkcjonalne:

Programowanie z elementami składowymi, które można łączyć ze sobą na wiele różnych sposobów.


źródło
12

Zastosowanie rozwiązania indexOf()będzie odpowiednie dla małych tablic, ale wraz ze wzrostem długości zbliża się wydajność algorytmu O(n^2). Oto rozwiązanie, które będzie działać lepiej dla bardzo dużych tablic, wykorzystując obiekty jako tablice asocjacyjne do przechowywania wpisów tablicy jako kluczy; eliminuje również automatycznie zduplikowane wpisy, ale działa tylko z wartościami ciągów (lub wartościami, które można bezpiecznie przechowywać jako ciągi znaków):

function arrayDiff(a1, a2) {
  var o1={}, o2={}, diff=[], i, len, k;
  for (i=0, len=a1.length; i<len; i++) { o1[a1[i]] = true; }
  for (i=0, len=a2.length; i<len; i++) { o2[a2[i]] = true; }
  for (k in o1) { if (!(k in o2)) { diff.push(k); } }
  for (k in o2) { if (!(k in o1)) { diff.push(k); } }
  return diff;
}

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
arrayDiff(a1, a2); // => ['c', 'd']
arrayDiff(a2, a1); // => ['c', 'd']
maerika
źródło
Chcesz używać Object.hasOwnProperty () za każdym razem, gdy wykonujesz „for in” na obiekcie. W przeciwnym razie istnieje ryzyko zapętlenia każdego pola dodanego do prototypu domyślnego obiektu. (Lub po prostu element nadrzędny obiektu) Również potrzebujesz tylko dwóch pętli, jednej do tworzenia tabeli skrótów, a drugiej do niej.
jholloman
1
@jholloman Z szacunkiem się nie zgadzam . Teraz, gdy możemy kontrolować wyliczanie dowolnej właściwości, prawdopodobnie należy uwzględnić dowolną właściwość uzyskaną podczas wyliczania.
Phrogz
1
@Phrogz Dobry punkt, jeśli martwisz się tylko o nowoczesne przeglądarki. Niestety muszę wrócić do IE7 w pracy, więc epoka kamienia to mój domyślny tok myślenia i nie używamy podkładek.
jholloman
10

Powyższa odpowiedź Joshaven Potter jest świetna. Ale zwraca elementy z tablicy B, których nie ma w tablicy C, ale nie na odwrót. Na przykład, jeśli var a=[1,2,3,4,5,6].diff( [3,4,5,7]);następnie wyświetli: ==> [1,2,6], ale nie [1,2,6,7] , co jest faktyczną różnicą między nimi. Nadal możesz użyć powyższego kodu Pottera, ale po prostu powtórz porównanie raz wstecz:

Array.prototype.diff = function(a) {
    return this.filter(function(i) {return !(a.indexOf(i) > -1);});
};

////////////////////  
// Examples  
////////////////////

var a=[1,2,3,4,5,6].diff( [3,4,5,7]);
var b=[3,4,5,7].diff([1,2,3,4,5,6]);
var c=a.concat(b);
console.log(c);

To powinno wygenerować: [ 1, 2, 6, 7 ]

użytkownik1685068
źródło
9

Kolejny sposób rozwiązania problemu

function diffArray(arr1, arr2) {
    return arr1.concat(arr2).filter(function (val) {
        if (!(arr1.includes(val) && arr2.includes(val)))
            return val;
    });
}

diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]);    // return [7, 4, 5]

Możesz także użyć składni funkcji strzałek:

const diffArray = (arr1, arr2) => arr1.concat(arr2)
    .filter(val => !(arr1.includes(val) && arr2.includes(val)));

diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]);    // return [7, 4, 5]
Iurii Golskyi
źródło
7
Array.prototype.difference = function(e) {
    return this.filter(function(i) {return e.indexOf(i) < 0;});
};

eg:- 

[1,2,3,4,5,6,7].difference( [3,4,5] );  
 => [1, 2, 6 , 7]
Riyas TK
źródło
Nigdy nie należy przedłużać obiektu rodzimego w ten sposób. Jeśli standard wprowadzi differencejako funkcję w przyszłej wersji, a ta funkcja będzie miała inną sygnaturę funkcji niż twoja, spowoduje to uszkodzenie kodu lub obcych bibliotek, które korzystają z tej funkcji.
t.niese
7

Bardzo proste rozwiązanie z funkcją filtrowania JavaScript:

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

function diffArray(arr1, arr2) {
  var newArr = [];
  var myArr = arr1.concat(arr2);
  
    newArr = myArr.filter(function(item){
      return arr2.indexOf(item) < 0 || arr1.indexOf(item) < 0;
    });
   alert(newArr);
}

diffArray(a1, a2);

Yash Thakkar
źródło
6

Co powiesz na to:

Array.prototype.contains = function(needle){
  for (var i=0; i<this.length; i++)
    if (this[i] == needle) return true;

  return false;
} 

Array.prototype.diff = function(compare) {
    return this.filter(function(elem) {return !compare.contains(elem);})
}

var a = new Array(1,4,7, 9);
var b = new Array(4, 8, 7);
alert(a.diff(b));

Więc w ten sposób możesz zrobić, array1.diff(array2)aby uzyskać ich różnicę (choć straszna złożoność czasu dla algorytmu - O (wydaje mi się, że macierz1.length x macierz2.length)

Kot
źródło
Użycie opcji filtrowania to świetny pomysł ... jednak nie musisz tworzyć metody zawierającej dla Array. Przekształciłem twój pomysł w jedną wkładkę ... Dzięki za inspirację!
Joshaven Potter
Nie musisz definiować funkcji zawiera (). JS zawiera () robi to samo.
Da Man,
4

Za pomocą http://phrogz.net/JS/ArraySetMath.js możesz:

var array1 = ["test1", "test2","test3", "test4"];
var array2 = ["test1", "test2","test3","test4", "test5", "test6"];

var array3 = array2.subtract( array1 );
// ["test5", "test6"]

var array4 = array1.exclusion( array2 );
// ["test5", "test6"]
Phrogz
źródło
4
function diffArray(arr1, arr2) {
  var newArr = arr1.concat(arr2);
  return newArr.filter(function(i){
    return newArr.indexOf(i) == newArr.lastIndexOf(i);
  });
}

to działa dla mnie

Stoisko
źródło
3
  • Rozwiązanie Pure JavaScript (bez bibliotek)
  • Kompatybilny ze starszymi przeglądarkami (nie używa filter)
  • O (n ^ 2)
  • Opcjonalny fnparametr wywołania zwrotnego, który pozwala określić sposób porównywania elementów tablicy

function diff(a, b, fn){
    var max = Math.max(a.length, b.length);
        d = [];
    fn = typeof fn === 'function' ? fn : false
    for(var i=0; i < max; i++){
        var ac = i < a.length ? a[i] : undefined
            bc = i < b.length ? b[i] : undefined;
        for(var k=0; k < max; k++){
            ac = ac === undefined || (k < b.length && (fn ? fn(ac, b[k]) : ac == b[k])) ? undefined : ac;
            bc = bc === undefined || (k < a.length && (fn ? fn(bc, a[k]) : bc == a[k])) ? undefined : bc;
            if(ac == undefined && bc == undefined) break;
        }
        ac !== undefined && d.push(ac);
        bc !== undefined && d.push(bc);
    }
    return d;
}

alert(
    "Test 1: " + 
    diff(
        [1, 2, 3, 4],
        [1, 4, 5, 6, 7]
      ).join(', ') +
    "\nTest 2: " +
    diff(
        [{id:'a',toString:function(){return this.id}},{id:'b',toString:function(){return this.id}},{id:'c',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
        [{id:'a',toString:function(){return this.id}},{id:'e',toString:function(){return this.id}},{id:'f',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
        function(a, b){ return a.id == b.id; }
    ).join(', ')
);

Trevor
źródło
Możesz buforować wartości długości, aby zmniejszyć prędkość. Chciałem zalecić dostęp do elementów tablicy bez sprawdzania długości, ale najwyraźniej ten prosty test daje prawie 100-krotne przyspieszenie.
Slotos,
Brak powodu do buforowania lengthwartości. To już zwykła właściwość. jsperf.com/array-length-caching
vp_arth
3

Działa to: po prostu scal dwie tablice, poszukaj duplikatów i wepchnij to, co nie jest duplikowane, do nowej tablicy, co jest różnicą.

function diff(arr1, arr2) {
  var newArr = [];
  var arr = arr1.concat(arr2);
  
  for (var i in arr){
    var f = arr[i];
    var t = 0;
    for (j=0; j<arr.length; j++){
      if(arr[j] === f){
        t++; 
        }
    }
    if (t === 1){
      newArr.push(f);
        }
  } 
  return newArr;
}

Giorgio Giuliani
źródło
3

// podejście es6

function diff(a, b) {
  var u = a.slice(); //dup the array
  b.map(e => {
    if (u.indexOf(e) > -1) delete u[u.indexOf(e)]
    else u.push(e)   //add non existing item to temp array
  })
  return u.filter((x) => {return (x != null)}) //flatten result
}
copremesis
źródło
3

Złożoność symetryczna i liniowa . Wymaga ES6.

function arrDiff(arr1, arr2) {
    var arrays = [arr1, arr2].sort((a, b) => a.length - b.length);
    var smallSet = new Set(arrays[0]);

    return arrays[1].filter(x => !smallSet.has(x));
}
Dodgie
źródło
2

Po prostu myślę ... ze względu na wyzwanie ;-) czy to zadziała ... (dla podstawowych tablic ciągów, liczb itp.) Brak tablic zagnieżdżonych

function diffArrays(arr1, arr2, returnUnion){
  var ret = [];
  var test = {};
  var bigArray, smallArray, key;
  if(arr1.length >= arr2.length){
    bigArray = arr1;
    smallArray = arr2;
  } else {
    bigArray = arr2;
    smallArray = arr1;
  }
  for(var i=0;i<bigArray.length;i++){
    key = bigArray[i];
    test[key] = true;
  }
  if(!returnUnion){
    //diffing
    for(var i=0;i<smallArray.length;i++){
      key = smallArray[i];
      if(!test[key]){
        test[key] = null;
      }
    }
  } else {
    //union
    for(var i=0;i<smallArray.length;i++){
      key = smallArray[i];
      if(!test[key]){
        test[key] = true;
      }
    }
  }
  for(var i in test){
    ret.push(i);
  }
  return ret;
}

array1 = "test1", "test2","test3", "test4", "test7"
array2 = "test1", "test2","test3","test4", "test5", "test6"
diffArray = diffArrays(array1, array2);
//returns ["test5","test6","test7"]

diffArray = diffArrays(array1, array2, true);
//returns ["test1", "test2","test3","test4", "test5", "test6","test7"]

Zauważ, że sortowanie prawdopodobnie nie będzie takie, jak wspomniano powyżej ... ale w razie potrzeby wywołaj funkcję .sort () na tablicy, aby ją posortować.

scunliffe
źródło
2

Chciałem podobnej funkcji, która przyjęła starą tablicę i nową tablicę i dała mi tablicę dodanych elementów i tablicę usuniętych elementów, i chciałem, aby była wydajna (więc nie. Zawiera!).

Możesz zagrać z moim proponowanym rozwiązaniem tutaj: http://jsbin.com/osewu3/12 .

Czy ktoś może zobaczyć jakiekolwiek problemy / ulepszenia tego algorytmu? Dzięki!

Lista kodów:

function diff(o, n) {
  // deal with empty lists
  if (o == undefined) o = [];
  if (n == undefined) n = [];

  // sort both arrays (or this won't work)
  o.sort(); n.sort();

  // don't compare if either list is empty
  if (o.length == 0 || n.length == 0) return {added: n, removed: o};

  // declare temporary variables
  var op = 0; var np = 0;
  var a = []; var r = [];

  // compare arrays and add to add or remove lists
  while (op < o.length && np < n.length) {
      if (o[op] < n[np]) {
          // push to diff?
          r.push(o[op]);
          op++;
      }
      else if (o[op] > n[np]) {
          // push to diff?
          a.push(n[np]);
          np++;
      }
      else {
          op++;np++;
      }
  }

  // add remaining items
  if( np < n.length )
    a = a.concat(n.slice(np, n.length));
  if( op < o.length )
    r = r.concat(o.slice(op, o.length));

  return {added: a, removed: r}; 
}
Ian Grainger
źródło
2

Szukałem prostej odpowiedzi, która nie wymagałaby używania różnych bibliotek, i wymyśliłem swoją własną, o której nie sądzę, żeby tu była wspomniana. Nie wiem, jak wydajna jest, ani nic, ale działa;

    function find_diff(arr1, arr2) {
      diff = [];
      joined = arr1.concat(arr2);
      for( i = 0; i <= joined.length; i++ ) {
        current = joined[i];
        if( joined.indexOf(current) == joined.lastIndexOf(current) ) {
          diff.push(current);
        }
      }
      return diff;
    }

W przypadku mojego kodu potrzebuję również duplikatów, ale myślę, że nie zawsze jest to preferowane.

Myślę, że główną wadą jest to, że potencjalnie porównuje wiele opcji, które zostały już odrzucone.

Samuel
źródło
2

poprawka littlebit dla najlepszej odpowiedzi

function arr_diff(a1, a2)
{
  var a=[], diff=[];
  for(var i=0;i<a1.length;i++)
    a[a1[i]]=a1[i];
  for(var i=0;i<a2.length;i++)
    if(a[a2[i]]) delete a[a2[i]];
    else a[a2[i]]=a2[i];
  for(var k in a)
   diff.push(a[k]);
  return diff;
}

weźmie to pod uwagę obecny typ elementu. b / c, gdy tworzymy [a1 [i]], konwertuje wartość na ciąg znaków z jej pierwotnej wartości, więc straciliśmy wartość rzeczywistą.

Vedanta-krit das
źródło
To nadal nie działa w przypadku tablicy obiektów. var a = [{a: 1}], b = [{b: 2}] arr_diff (a, b) == [].
EricP