Каков самый быстрый или самый элегантный способ вычисления разности множеств с помощью массивов Javascript?
пусть A
и B
быть два комплекта. Я ищу действительно быстрые или элегантные способы вычисления заданной разницы (A - B
или A B
, в зависимости от ваших предпочтений) между ними. Эти два набора хранятся и обрабатываются как массивы Javascript, как говорится в названии.
Примечания:
- Гекко-специфические трюки в порядке
- Я бы предпочел придерживаться собственных функций (но я открыт для легкой библиотеки, если это так быстрее)
- Я видел, но не проверял, JS.Набор (см. предыдущий пункт)
Edit: я заметил комментарий о наборах, содержащих повторяющиеся элементы. Когда я говорю "набор", я имею в виду математическое определение, которое означает (среди прочего), что они не содержат повторяющихся элементов.
10 ответов:
если не знаю, если это наиболее эффективно, но, возможно, самый короткий
A = [1, 2, 3, 4]; B = [1, 3, 4, 7]; diff = A.filter(function(x) { return B.indexOf(x) < 0 }) console.log(diff);
обновлено до ES6:
A = [1, 2, 3, 4]; B = [1, 3, 4, 7]; diff = A.filter(x => B.indexOf(x) < 0 ); console.log(diff);
Ну, 7 лет спустя, с набор ES6 объект это довольно легко (но все же не так компактно, как pythons A-B), и, как сообщается, быстрее, чем
indexOf
для больших массивов:let a = new Set([1,2,3,4]); let b = new Set([5,4,3,2]); console.log(new Set([...a].filter(x => !b.has(x)))); //a\b => {1} console.log(new Set([...b].filter(x => !a.has(x)))); //b\a => {5} console.log(new Set([...a].filter(x => b.has(x)))); //a∩b => {2,3,4}
вы можете использовать объект в качестве Карты, чтобы избежать линейно сканирования
B
для каждого элементаA
а в ответ пользователя 187291:function setMinus(A, B) { var map = {}, C = []; for(var i = B.length; i--; ) map[B[i].toSource()] = null; // any other value would do for(var i = A.length; i--; ) { if(!map.hasOwnProperty(A[i].toSource())) C.push(A[i]); } return C; }
нестандартное
toSource()
метод используется для получения уникальных имен свойств; если все элементы уже имеют уникальные строковые представления (как в случае с числами), вы можете ускорить код, отбросивtoSource()
вызовы.
самый короткий, используя jQuery, это:
var A = [1, 2, 3, 4]; var B = [1, 3, 4, 7]; var diff = $(A).not(B); console.log(diff.toArray());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Я бы хэшировал массив B, а затем сохранял значения из массива a, отсутствующего в B:
function getHash(array){ // Hash an array into a set of properties // // params: // array - (array) (!nil) the array to hash // // return: (object) // hash object with one property set to true for each value in the array var hash = {}; for (var i=0; i<array.length; i++){ hash[ array[i] ] = true; } return hash; } function getDifference(a, b){ // compute the difference a\b // // params: // a - (array) (!nil) first array as a set of values (no duplicates) // b - (array) (!nil) second array as a set of values (no duplicates) // // return: (array) // the set of values (no duplicates) in array a and not in b, // listed in the same order as in array a. var hash = getHash(b); var diff = []; for (var i=0; i<a.length; i++){ var value = a[i]; if ( !hash[value]){ diff.push(value); } } return diff; }
включая идею Кристофа и предполагая несколько нестандартных методов итерации на массивах и объектах / хэшах (
each
и друзья), мы можем получить заданную разницу, объединение и пересечение в линейном времени примерно в 20 строках всего:var setOPs = { minusAB : function (a, b) { var h = {}; b.each(function (v) { h[v] = true; }); return a.filter(function (v) { return !h.hasOwnProperty(v); }); }, unionAB : function (a, b) { var h = {}, f = function (v) { h[v] = true; }; a.each(f); b.each(f); return myUtils.keys(h); }, intersectAB : function (a, b) { var h = {}; a.each(function (v) { h[v] = 1; }); b.each(function (v) { h[v] = (h[v] || 0) + 1; }); var fnSel = function (v, count) { return count > 1; }; var fnVal = function (v, c) { return v; }; return myUtils.select(h, fnSel, fnVal); } };
это предполагает, что
each
иfilter
определены для массивов, и что у нас есть два вспомогательных методов:
myUtils.keys(hash)
: возвращает массив с ключами хэш
myUtils.select(hash, fnSelector, fnEvaluator)
: возвращает массив с результаты вызоваfnEvaluator
о парах ключ / значение, для которыхfnSelector
возвращает true.The
select()
слабо вдохновлен Common Lisp, и это простоfilter()
иmap()
в одном флаконе. (Было бы лучше, чтобы они были определены наObject.prototype
, но это разрушает хаос с jQuery, поэтому я согласился на статические методы утилиты.)Производительности: Тестирование с
var a = [], b = []; for (var i = 100000; i--; ) { if (i % 2 !== 0) a.push(i); if (i % 3 !== 0) b.push(i); }
дает два набора с 50,000 и 66,666 элементами. При этих значениях A-B занимает около 75 мс, в то время как объединение и пересечение составляют около 150 мс каждый. (Mac Safari 4.0, используя Javascript Date для синхронизации.)
Я думаю, что это приличный выигрыш для 20 строк кода.
С помощью подчеркивания.js (библиотека для функциональных JS)
>>> var foo = [1,2,3] >>> var bar = [1,2,4] >>> _.difference(foo, bar); [4]
это работает, но я думаю, что другой гораздо короче, и элегантный тоже
A = [1, 'a', 'b', 12]; B = ['a', 3, 4, 'b']; diff_set = { ar : {}, diff : Array(), remove_set : function(a) { ar = a; return this; }, remove: function (el) { if(ar.indexOf(el)<0) this.diff.push(el); } } A.forEach(diff_set.remove_set(B).remove,diff_set); C = diff_set.diff;
Что касается постного пути, это не так элегантно, но я провел несколько тестов, чтобы быть уверенным. Загрузка одного массива в качестве объекта намного быстрее для обработки в больших количествах:
var t, a, b, c, A; // Fill some arrays to compare a = Array(30000).fill(0).map(function(v,i) { return i.toFixed(); }); b = Array(20000).fill(0).map(function(v,i) { return (i*2).toFixed(); }); // Simple indexOf inside filter t = Date.now(); c = b.filter(function(v) { return a.indexOf(v) < 0; }); console.log('completed indexOf in %j ms with result %j length', Date.now() - t, c.length); // Load `a` as Object `A` first to avoid indexOf in filter t = Date.now(); A = {}; a.forEach(function(v) { A[v] = true; }); c = b.filter(function(v) { return !a[v]; }); console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);
результаты:
completed indexOf in 1219 ms with result 5000 length completed Object in 8 ms with result 5000 length
однако, это работает с строки только. Если вы планируете сравнить пронумерованные наборы, вы хотите сопоставить результаты с parseInt.
некоторые простые функции, заимствуя из ответа @milan:
const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x))); const setIntersection = (a, b) => new Set([...a].filter(x => b.has(x))); const setUnion = (a, b) => new Set([...a, ...b]);
использование:
const a = new Set([1, 2]); const b = new Set([2, 3]); setDifference(a, b); // Set { 1 } setIntersection(a, b); // Set { 2 } setUnion(a, b); // Set { 1, 2, 3 }