Как получить разницу между двумя массивами в JavaScript?


755

Есть ли способ вернуть разницу между двумя массивами в JavaScript?

Например:

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

// need ["c", "d"]

9
Симметричный или несимметричный?
Гонки легкости на орбите

С новой функцией ES6 это можно сделать простым однострочником (его использование во всех основных браузерах займет много времени). В любом случае проверьте мой ответ
Сальвадор Дали

1
Важным аспектом решения является производительность. асимптотическая временная сложность этого типа операции - на других языках - есть O(a1.length x log(a2.length))- возможна ли эта производительность в JavaScript?
Рауль

Ответы:


219

Я предполагаю, что вы сравниваете нормальный массив. Если нет, вам нужно изменить цикл for на цикл 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"));

Лучшее решение, если вам не нужна обратная совместимость, - это использование фильтра. Но все же это решение работает.


46
Это может работать, но выполняет три цикла, чтобы выполнить то, что можно сделать в одной строке кода, используя метод filter Array.
Джошавен Поттер

9
Просто чтобы быть ясно, это реализует симметричная разность по а1 и а2 , в отличие от других ответов опубликованы здесь.
200

25
Это не лучший ответ, но я даю ему благотворительный ответ, чтобы помочь компенсировать несправедливые негативные ответы. Только неправильные ответы должны быть опущены, и если бы я работал над проектом с браузерами-фреймворками (бывают трудные времена), этот ответ мог бы даже оказаться полезным.
Майкл Шепер

3
Могу ли я узнать , что произойдет , когда var a1 = ['a', 'b'];и var a2 = ['a', 'b', 'c', 'd', 'b'];, он будет возвращать неверный ответ , то есть ['c', 'd', 'b']вместо ['c', 'd'].
skbly7

4
Самый быстрый способ - это наиболее очевидное наивное решение. Я проверил все предложенные решения для симметричного сравнения в этой теме, и победитель: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; }
Кочевник

1237

Есть лучший способ использования ES7:


пересечение

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

Разница пересечений Диаграмма Венна

Ибо [1,2,3] [2,3]это даст [2,3]. С другой стороны, for [1,2,3] [2,3,5]вернет то же самое.


разница

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

Правильная разница Венна Диаграмма

Ибо [1,2,3] [2,3]это даст [1]. С другой стороны, for [1,2,3] [2,3,5]вернет то же самое.


Для симметричной разницы вы можете сделать:

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

Симметричная разность Венна Диаграмма

Таким образом, вы получите массив, содержащий все элементы arr1, которых нет в arr2, и наоборот

Как отметил @Joshaven Potter в своем ответе, вы можете добавить это в Array.prototype, чтобы его можно было использовать следующим образом:

Array.prototype.diff = function(arr2) { return this.filter(x => !arr2.includes(x)); }
[1, 2, 3].diff([2, 3])

3
Я предпочитаю проверять < 0вместо== -1
Вик

1
Вычисление Arrayразницы является так называемым set operation, потому что поиск свойства - это собственная работа Sets, которая на несколько порядков быстрее, чем indexOf/ includes. Проще говоря, ваше решение очень неэффективно и довольно медленно.

@ftor но с Set, значения должны быть уникальными, нет?
CervEd

1
@ LuisSieira Я понимаю, что это сработает, [1,2,3] [2,3,5]учитывая, что числа уникальны, но если бы вы сказали [1,1,2,3] [1,2,3,5]и ожидали, [1]что не сможете использовать Set. Ваше решение тоже не сработает: - / Я закончил создавать эту функцию, потому что не мог найти удовлетворительный способ сделать это более кратко. Если у вас есть идеи, как это сделать, я хотел бы знать!
CervEd

3
Не является Array.includes()ли функция ES7 вместо ES6? (1) (2) - и для продолжения ES6 вы можете использовать, Array.some()например let intersection = aArray.filter(a => bArray.some(b => a === b)), нет?
Яри ​​Кейнянен

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"]

Обратите внимание, что indexOf и фильтр недоступны в ie до ie9.


50
Единственный браузер, который не поддерживает filter и indexOf, это IE8. IE9 поддерживает их обоих. Так что это не так.
Брайан Ларсен

14
ie7 и ie8 по-прежнему (к сожалению) все еще очень актуальны, однако вы можете найти многозаполненный код для обеих функций на сайте MDN: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… developer.mozilla.org/en/JavaScript/ Ссылка / Global_Objects /… Загрузите код, указанный в разделе «Совместимость», через условный IE и BOOM. Т.е. 7/8 поддерживаются.
1nfiniti

44
У этого решения есть время выполнения O (n ^ 2), линейное решение было бы намного более эффективным.
Джолломан

75
Если вы используете функцию, подобную этой: [1,2,3].diff([3,4,5])она вернется [1,2]вместо того, [1,2,4,5]чтобы не решить проблему в исходном вопросе, о чем следует знать.
Bugster

12
@AlinPurcaru Не поддерживается архаичными браузерами! = Неправильно. Учитывая Netscape 2.0, большая часть кода JS здесь «неправильна» по этому определению. Это глупо говорить.
NullUserException

304

На сегодняшний день это самый простой способ получить именно тот результат, который вы ищете, используя jQuery:

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

diffтеперь содержит то, что было в old_arrayтом, что не вnew_array


4
@Batman Да, но только если они являются ссылками на один и тот же объект ( {a: 1} != {a: 1}) ( доказательство )
Matmarbon

Можно ли использовать его с массивами, содержащими данные пользовательского объекта? Я попробовал это на самом деле так же, но это не сработало. Любые идеи будут очень заметны.
LetMeCodeYou

8
Это трюк? В документе этот метод рассматривается как часть методов элемента DOM, а не как вспомогательный элемент общего массива. Так что это может работать так сейчас, но, возможно, не в будущих версиях, так как не предполагалось использовать его таким образом. Хотя, я был бы рад, если бы это был официальный помощник по массивам.
robsch

1
@robsch Когда вы используете .notмассив, jQuery использует его встроенную утилиту, .grep()специально предназначенную для фильтрации массивов. Я не вижу этого изменения.
суперфонический

1
@ vsync Звучит так, как будто ты после симметричной разницы
суперфонический

158

Разностный метод в Underscore (или его замена, Lo-Dash ) также может сделать это:

(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]

Как и с любой функцией Underscore, вы также можете использовать ее в более объектно-ориентированном стиле:

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

4
Я думаю, что это хорошее решение с точки зрения производительности, тем более что lodash и underscore продолжают бороться за лучшую реализацию. Кроме того, он совместим с IE6.
Mahemoff

4
Осторожно, эта реализация не будет работать для массивов объектов. См. Stackoverflow.com/q/8672383/14731 для получения дополнительной информации.
Гили

1
Как упоминается в одном из ответов, он работает, если это один и тот же объект, но не, если два объекта имеют одинаковые свойства. Я думаю, что это нормально, так как понятия равенства меняются (например, это также может быть атрибутом «id» в некоторых приложениях). Однако было бы хорошо, если бы вы могли пройти сравнительный тест для intersect ().
mahemoff

Для потомков: у Lodash теперь есть _.differenceBy (), который принимает обратный вызов для сравнения; Если вы сравниваете объекты, вы можете добавить функцию, которая сравнивает их так, как вам нужно.
SomeCallMeTim

2
Будьте осторожны, если порядок аргументов поменялся местами, он не сработает. например. _.difference ([5, 2, 10], [1, 2, 3, 4, 5]); не могу получить diff
Russj

79

Простой JavaScript

Есть два возможных объяснения «разницы». Я позволю вам выбрать, какой вы хотите. Скажем, у вас есть:

var a1 = ['a', 'b'     ];
var a2 = [     'b', 'c'];
  1. Если вы хотите получить ['a'], используйте эту функцию:

    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. Если вы хотите получить ['a', 'c'](все элементы, содержащиеся в одном a1 или a2, но не в обоих - так называемое симметричное различие ), используйте эту функцию:

    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, вы можете использовать _.difference(a1, a2)(случай 1 выше) или _.xor(a1, a2)(случай 2).

Если вы используете Underscore.js, вы можете использовать _.difference(a1, a2) функцию для случая 1.

ES6 Set, для очень больших массивов

Код выше работает во всех браузерах. Однако для больших массивов, состоящих из более чем 10 000 элементов, он становится довольно медленным, поскольку имеет сложность O (n²). Во многих современных браузерах мы можем использовать Setобъект ES6 для ускорения работы. Lodash автоматически использует, Setкогда он доступен. Если вы не используете lodash, используйте следующую реализацию, вдохновленную сообщением в блоге Акселя Раушмайера :

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

Ноты

Поведение для всех примеров может быть удивительным или неочевидным, если вы заботитесь о -0, +0, NaN или разреженных массивах. (Для большинства случаев это не имеет значения.)


Поблагодарить. ты спас мой день Я должен был сравнить массивы 300K, и ваше решение "Set" сработало отлично. Это должен быть принятый ответ.
justadev

52

Чтобы получить симметричную разницу, вам нужно сравнивать массивы обоими способами (или во всех случаях в случае нескольких массивов).

введите описание изображения здесь


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

Пример:

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

Разница между массивами объектов

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

Пример:

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

51

Более чистый подход в ES6 является следующим решением.

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

разница

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

пересечение

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

Дизъюнктивный союз (симметричное различие)

[ ...a2.filter(d => !a1.includes(d)),
  ...a1.filter(d => !a2.includes(d)) ]

Работает только в одном направлении. Теперь представьте себе , что a1 = ['a', 'b', 'e']: е не будут извлечены.
Имрок

да, именно так работает разница в теории множеств. (a2 -a1) вы ищете (a2-a1) + (a1-a2)
ifelse.codes

1
@imrok Я считаю, что это то, что вы ищете [... a2.filter (d =>! a1.include (d)), ... (a1.filter (d =>! a2.include (d)) )]
ifelse.codes

2
Прекрасное решение, спасибо!
Тьяго Алвес

40

Вы можете использовать набор в этом случае. Он оптимизирован для такого рода операций (объединение, пересечение, разность).

Убедитесь, что это применимо к вашему делу, как только оно не допускает дублирования.

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}

4
Это похоже на хорошую библиотеку! Как жаль, что вы не можете загрузить только Setфункцию, не имея необходимости получать все остальное ...
Блист

@Blixt Я полагаю, что вы можете скачать все это, и просто включить только файл set.js
Сэмюэль Каррихо

Набор также реализован в Google. closure-library.googlecode.com/svn/docs/…
Бен Флинн


32
function diff(a1, a2) {
  return a1.concat(a2).filter(function(val, index, arr){
    return arr.indexOf(val) === arr.lastIndexOf(val);
  });
}

Объедините оба массива, уникальные значения появятся только один раз, поэтому indexOf () будет таким же, как lastIndexOf ().


3
Я согласен, что это самый чистый и простой способ, и приятно, что он не требует трогательного прототипа. «Если вы не можете объяснить это шестилетнему ребенку, вы сами этого не понимаете». - Альберт Эйнштейн
lacostenycoder

18

чтобы вычесть один массив из другого, просто используйте фрагмент ниже:

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

Он вернет ['1,' 2 ',' 6 '], которые являются элементами первого массива, которые не существуют во втором.

Следовательно, в соответствии с примером вашей проблемы, следующий код является точным решением:

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

14

С появлением ES6 с множествами и оператором сплат (на момент работы только в Firefox, проверьте таблицу совместимости ), вы можете написать следующую строку:

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)))];

что приведет к [ "c", "d" ].


Просто любопытно, как это отличается от деланияb.filter(x => !a.indexOf(x)))
чови

1
@chovy это отличается во времени сложности. Мое решение - O(n + m)ваше решение, O(n * m)где n и m - длины массивов. Возьмите длинные списки, и мое решение будет запущено в считанные секунды, а ваше - часами.
Сальвадор Дали

Как насчет сравнения атрибута списка объектов? Возможно ли это с помощью этого решения?
Чови

2
a.filter(x => !b1.has(x))проще И обратите внимание, спецификация требует, чтобы сложность была в среднем n * f(m) + mс f(m)сублинейной. Это лучше чем n * m, но не обязательно n + m.
Oriol

2
@SalvadorDali var difference = [...new Set([...a].filter(x => !b1.has(x)))];Почему вы создаете дубликат массива 'a'? Почему вы превращаете результат фильтра в набор, а затем обратно в массив? Разве это не эквивалентноvar difference = a.filter(x => !b1.has(x));
Дипак Миттал

13

Функциональный подход с ES2015

Вычисление differenceдвух массивов является одной из Setопераций. Термин уже указывает, что Setдля увеличения скорости поиска следует использовать собственный тип. В любом случае, при вычислении разницы между двумя наборами есть три перестановки:

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

Вот функциональное решение, которое отражает эти перестановки.

Слева 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) );

Право difference:

differencerтривиально Это просто differencelс перевернутыми аргументами. Вы можете написать функцию для удобства:const differencer = flip(differencel) . Это все!

Симметричный difference:

Теперь, когда у нас есть левый и правый, реализация симметрии также differenceстановится тривиальной:

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

Я полагаю, что этот пример является хорошей отправной точкой, чтобы получить представление о том, что означает функциональное программирование:

Программирование со строительными блоками, которые могут быть соединены вместе различными способами.


12

Использование решения indexOf()будет хорошо для небольших массивов, но по мере увеличения их длины производительность алгоритма приближается O(n^2). Вот решение, которое будет работать лучше для очень больших массивов, используя объекты в качестве ассоциативных массивов для хранения записей массива в качестве ключей; он также автоматически удаляет повторяющиеся записи, но работает только со строковыми значениями (или со значениями, которые можно безопасно хранить в виде строк):

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']

Вы хотите использовать Object.hasOwnProperty () всякий раз, когда вы делаете «для» объекта. В противном случае вы рискуете перебрать все поля, добавленные к прототипу объекта по умолчанию. (Или просто родитель вашего объекта) Также вам нужны только два цикла, один для создания хеш-таблицы, а другой ищет эту хеш-таблицу.
Джолломан

1
@jholloman Я с уважением не согласен . Теперь, когда мы можем контролировать перечислимость для любого свойства, вероятно, вы должны включать любое свойство, которое вы получаете во время перечисления.
Phrogz

1
@Phrogz Хорошо, если вы беспокоитесь только о современных браузерах. К сожалению, я должен вернуться к IE7 на работе, поэтому каменный век - мой ход мыслей по умолчанию, и мы не склонны использовать прокладки.
Джолломан

10

Приведенный выше ответ Джошавена Поттера великолепен. Но он возвращает элементы в массиве B, которых нет в массиве C, но не наоборот. Например, если var a=[1,2,3,4,5,6].diff( [3,4,5,7]);тогда он выдаст: ==> [1,2,6], но нет [1,2,6,7] , что является фактической разницей между ними. Вы все еще можете использовать приведенный выше код Поттера, но просто повторить сравнение и в обратном направлении:

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

Это должно вывести: [ 1, 2, 6, 7 ]


9

Еще один способ решить проблему

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]

Также вы можете использовать синтаксис функции стрелки:

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]

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]

Вы никогда не должны расширять нативный объект таким образом. Если стандарт будет представлен differenceкак функция в будущей версии, и тогда эта функция будет иметь другую сигнатуру функции, чем ваша, это нарушит ваш код или сторонние библиотеки, использующие эту функцию.
t.niese

7

Очень простое решение с функцией фильтра 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);


6

Как насчет этого:

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

Таким образом, вы можете сделать так, array1.diff(array2)чтобы получить их разницу (хотя ужасная сложность времени для алгоритма - O (array1.length x array2.length) я считаю)


Использование параметра фильтра - отличная идея ... однако вам не нужно создавать метод содержимого для массива. Я превратил вашу идею в один лайнер ... Спасибо за вдохновение!
Джошавен Поттер

Вам не нужно определять функцию contains (). JS включает в себя () делает то же самое.
Да Ман

4

Используя http://phrogz.net/JS/ArraySetMath.js, вы можете:

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"]

4
function diffArray(arr1, arr2) {
  var newArr = arr1.concat(arr2);
  return newArr.filter(function(i){
    return newArr.indexOf(i) == newArr.lastIndexOf(i);
  });
}

это работает для меня


3
  • Чистое решение JavaScript (без библиотек)
  • Совместим со старыми браузерами (не использует filter)
  • O (N ^ 2)
  • Необязательный fnпараметр обратного вызова, который позволяет указать, как сравнивать элементы массива.

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(', ')
);


Вы можете кэшировать значения длины, чтобы увеличить скорость. Я хотел порекомендовать доступ к элементам массива без проверки длины, но очевидно, что простая проверка дает почти 100-кратное ускорение.
Слоты

Нет причин для кэширования lengthзначений. Это уже обычная собственность. jsperf.com/array-length-caching
vp_arth

3

Это работает: в основном объедините два массива, найдите дубликаты и поместите то, что не дублируется, в новый массив, который является разницей.

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


3

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

3

Симметричная и линейная сложность . Требуется 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));
}

3

еще один ответ, но, кажется, никто не упомянул jsperf, где они сравнивают несколько алгоритмов и техническую поддержку: https://jsperf.com/array-difference-javascript, кажется, использование фильтра дает лучшие результаты. Спасибо


2

Просто думать ... ради вызова ;-) будет ли это работать ... (для базовых массивов строк, чисел и т. Д.) Нет вложенных массивов

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"]

Обратите внимание, что сортировка, вероятно, будет не такой, как указано выше ... но при желании вызовите .sort () для массива, чтобы отсортировать ее.


2

Я хотел подобную функцию, которая использовала бы старый массив и новый массив и давал мне массив добавленных элементов и массив удаленных элементов, и я хотел, чтобы он был эффективным (поэтому нет .contains!).

Вы можете поиграть с моим предложенным решением здесь: http://jsbin.com/osewu3/12 .

Кто-нибудь может увидеть какие-либо проблемы / улучшения этого алгоритма? Спасибо!

Список кодов:

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

2

Я искал простой ответ, который не предполагал использование разных библиотек, и я нашел свой собственный, который, я думаю, не был упомянут здесь. Я не знаю, насколько это эффективно или что-то еще, но это работает;

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

Для моего кода мне также нужно удалить дубликаты, но я думаю, что это не всегда предпочтительно.

Я предполагаю, что основным недостатком является то, что он потенциально сравнивает многие варианты, которые уже были отклонены.


2

маленький бит исправить для лучшего ответа

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

это будет учитывать текущий тип элемента. b / c когда мы создаем [a1 [i]], он преобразует значение в строку из его первоначального значения, поэтому мы потеряли фактическое значение.


Это все еще не удается для массива объектов. var a = [{a: 1}], b = [{b: 2}] arr_diff (a, b) == [].
EricP
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.