Самый простой код для пересечения массивов в javascript
какой самый простой, без библиотеки код для реализации пересечений массивов в javascript? Я хочу написать
intersection([1,2,3], [2,3,4,5])
и вам
[2, 3]
30 ответов:
используйте комбинацию
Array.prototype.filter
иArray.prototype.indexOf
:array1.filter(value => -1 !== array2.indexOf(value));
разрушение кажется самым простым, особенно если мы можем предположить, что вход отсортирован:
/* destructively finds the intersection of * two arrays in a simple fashion. * * PARAMS * a - first array, must already be sorted * b - second array, must already be sorted * * NOTES * State of input arrays is undefined when * the function returns. They should be * (prolly) be dumped. * * Should have O(n) operations, where n is * n = MIN(a.length, b.length) */ function intersection_destructive(a, b) { var result = []; while( a.length > 0 && b.length > 0 ) { if (a[0] < b[0] ){ a.shift(); } else if (a[0] > b[0] ){ b.shift(); } else /* they're equal */ { result.push(a.shift()); b.shift(); } } return result; }
Неразрушающий должен быть волос сложнее, так как мы должны отслеживать индексы:
/* finds the intersection of * two arrays in a simple fashion. * * PARAMS * a - first array, must already be sorted * b - second array, must already be sorted * * NOTES * * Should have O(n) operations, where n is * n = MIN(a.length(), b.length()) */ function intersect_safe(a, b) { var ai=0, bi=0; var result = []; while( ai < a.length && bi < b.length ) { if (a[ai] < b[bi] ){ ai++; } else if (a[ai] > b[bi] ){ bi++; } else /* they're equal */ { result.push(a[ai]); ai++; bi++; } } return result; }
если ECMAScript 6 Set, один простой и предположительно эффективный (см. ссылку на спецификацию) способ:
function intersect(a, b) { var setA = new Set(a); var setB = new Set(b); var intersection = new Set([...setA].filter(x => setB.has(x))); return Array.from(intersection); }
короче, но менее читаемый (также без создания дополнительного пересечения
Set
):function intersect(a, b) { return [...new Set(a)].filter(x => new Set(b).has(x)); }
обратите внимание, что реализация набора позволит только уникальные значения, таким образом
new Set[1,2,3,3].size
значение3
.
Как насчет просто с помощью ассоциативных массивов?
function intersect(a, b) { var d1 = {}; var d2 = {}; var results = []; for (var i = 0; i < a.length; i++) { d1[a[i]] = true; } for (var j = 0; j < b.length; j++) { d2[b[j]] = true; } for (var k in d1) { if (d2[k]) results.push(k); } return results; }
edit:
// new version function intersect(a, b) { var d = {}; var results = []; for (var i = 0; i < b.length; i++) { d[b[i]] = true; } for (var j = 0; j < a.length; j++) { if (d[a[j]]) results.push(a[j]); } return results; }
мой вклад в терминах ES6. В общем случае он находит пересечение массива с неопределенным числом массивов, предоставленных в качестве аргументов.
Array.prototype.intersect = function(...a) { return [this,...a].reduce((p,c) => p.filter(e => c.includes(e))); } var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]], arr = [0,1,2,3,4,5,6,7,8,9]; document.write("<pre>" + JSON.stringify(arr.intersect(...arrs)) + "</pre>");
производительность реализации @atk для отсортированных массивов примитивов может быть улучшена с помощью .скорее поп, чем .сдвиг.
function intersect(array1, array2) { var result = []; // Don't destroy the original arrays var a = array1.slice(0); var b = array2.slice(0); var aLast = a.length - 1; var bLast = b.length - 1; while (aLast >= 0 && bLast >= 0) { if (a[aLast] > b[bLast] ) { a.pop(); aLast--; } else if (a[aLast] < b[bLast] ){ b.pop(); bLast--; } else /* they're equal */ { result.push(a.pop()); b.pop(); aLast--; bLast--; } } return result; }
Я создал бенчмарк с помощью jsPerf:http://bit.ly/P9FrZK. это примерно в три раза быстрее в использовании.поп.
- Сортировать
- проверьте один за другим из индекса 0, создайте новый массив из этого.
что-то вроде этого, хотя и не проверены.
function intersection(x,y){ x.sort();y.sort(); var i=j=0;ret=[]; while(i<x.length && j<y.length){ if(x[i]<y[j])i++; else if(y[j]<x[i])j++; else { ret.push(x[i]); i++,j++; } } return ret; } alert(intersection([1,2,3], [2,3,4,5]));
PS: алгоритм предназначен только для чисел и обычных строк, пересечение массивов объектов arbitary может не работать.
для массивов, содержащих только строки или числа вы можете сделать что-то с сортировкой, как в некоторых других ответов. Для общего случая массивов произвольных объектов я не думаю, что вы можете избежать этого долгого пути. Следующее даст вам пересечение любого количества массивов, предоставленных в качестве параметров для
arrayIntersection
:var arrayContains = Array.prototype.indexOf ? function(arr, val) { return arr.indexOf(val) > -1; } : function(arr, val) { var i = arr.length; while (i--) { if (arr[i] === val) { return true; } } return false; }; function arrayIntersection() { var val, arrayCount, firstArray, i, j, intersection = [], missing; var arrays = Array.prototype.slice.call(arguments); // Convert arguments into a real array // Search for common values firstArray = arrays.pop(); if (firstArray) { j = firstArray.length; arrayCount = arrays.length; while (j--) { val = firstArray[j]; missing = false; // Check val is present in each remaining array i = arrayCount; while (!missing && i--) { if ( !arrayContains(arrays[i], val) ) { missing = true; } } if (!missing) { intersection.push(val); } } } return intersection; } arrayIntersection( [1, 2, 3, "a"], [1, "a", 2], ["a", 1] ); // Gives [1, "a"];
// Return elements of array a that are also in b in linear time: function intersect(a, b) { return a.filter(Set.prototype.has, new Set(b)); } // Example: console.log(intersect([1,2,3], [2,3,4,5]));
Я рекомендую выше краткое решение, которое превосходит другие реализации на больших входах. Если производительность на малых входах имеет значение, проверьте альтернативы ниже.
альтернативы и сравнение производительности:
см. следующий фрагмент для альтернативных реализаций и проверьте https://jsperf.com/array-intersection-comparison для представления сравнения.
function intersect_for(a, b) { const result = []; const alen = a.length; const blen = b.length; for (let i = 0; i < alen; ++i) { const ai = a[i]; for (let j = 0; j < blen; ++j) { if (ai === b[j]) { result.push(ai); break; } } } return result; } function intersect_filter_indexOf(a, b) { return a.filter(el => b.indexOf(el) !== -1); } function intersect_filter_in(a, b) { const map = b.reduce((map, el) => {map[el] = true; return map}, {}); return a.filter(el => el in map); } function intersect_for_in(a, b) { const result = []; const map = {}; for (let i = 0, length = b.length; i < length; ++i) { map[b[i]] = true; } for (let i = 0, length = a.length; i < length; ++i) { if (a[i] in map) result.push(a[i]); } return result; } function intersect_filter_includes(a, b) { return a.filter(el => b.includes(el)); } function intersect_filter_has_this(a, b) { return a.filter(Set.prototype.has, new Set(b)); } function intersect_filter_has_arrow(a, b) { const set = new Set(b); return a.filter(el => set.has(el)); } function intersect_for_has(a, b) { const result = []; const set = new Set(b); for (let i = 0, length = a.length; i < length; ++i) { if (set.has(a[i])) result.push(a[i]); } return result; }
результаты в Firefox 53:
Ops / sec на больших массивах (10 000 элементов):
filter + has (this) 523 (this answer) for + has 482 for-loop + in 279 filter + in 242 for-loops 24 filter + includes 14 filter + indexOf 10
Ops / sec на малых массивах (100 элементов):
for-loop + in 384,426 filter + in 192,066 for-loops 159,137 filter + includes 104,068 filter + indexOf 71,598 filter + has (this) 43,531 (this answer) filter + has (arrow function) 35,588
он довольно короткий, используя ES2015 и наборы. Принимает массивов значения как строку и удаляет дубликаты.
let intersection = function(a, b) { a = new Set(a), b = new Set(b); return [...a].filter(v => b.has(v)); }; console.log(intersection([1,2,1,2,3], [2,3,5,4,5,3])); console.log(intersection('ccaabbab', 'addb').join(''));
крошечная настройка до самого маленького здесь ( filter / indexOf solution), а именно создание индекса значений в одном из массивов с помощью объекта JavaScript, уменьшит его с O(N*M) до "вероятно" линейного времени. source1 source2
function intersect(a, b) { var aa = {}; a.forEach(function(v) { aa[v]=1; }); return b.filter(function(v) { return v in aa; }); }
Это не самое простое решение (это больше кода, чем filter+indexOf), и это не самый быстрый (вероятно, медленнее на постоянный фактор, чем intersect_safe ()), но, похоже, довольно хороший баланс. Он находится на очень простая сторона, обеспечивая при этом хорошую производительность, и она не требует предварительно отсортированных входов.
function intersection(A,B){ var result = new Array(); for (i=0; i<A.length; i++) { for (j=0; j<B.length; j++) { if (A[i] == B[j] && $.inArray(A[i],result) == -1) { result.push(A[i]); } } } return result; }
С некоторыми ограничениями на ваши данные, вы можете сделать это линейный время!
на - целые положительные числа: используйте массив, сопоставляющий значения с логическим значением" видно/не видно".
function intersectIntegers(array1,array2) { var seen=[], result=[]; for (var i = 0; i < array1.length; i++) { seen[array1[i]] = true; } for (var i = 0; i < array2.length; i++) { if ( seen[array2[i]]) result.push(array2[i]); } return result; }
существует аналогичная техника для объекты: возьмите фиктивный ключ, установите его в "true" для каждого элемента в array1, а затем найдите этот ключ в элементах array2. Убирайся, когда закончишь.
function intersectObjects(array1,array2) { var result=[]; var key="tmpKey_intersect" for (var i = 0; i < array1.length; i++) { array1[i][key] = true; } for (var i = 0; i < array2.length; i++) { if (array2[i][key]) result.push(array2[i]); } for (var i = 0; i < array1.length; i++) { delete array1[i][key]; } return result; }
конечно, вы должны быть уверены в ключ раньше не появлялся, иначе вы будете уничтожать свои данные...
еще один индексированный подход, способный обрабатывать любое количество массивов сразу:
// Calculate intersection of multiple array or object values. function intersect (arrList) { var arrLength = Object.keys(arrList).length; // (Also accepts regular objects as input) var index = {}; for (var i in arrList) { for (var j in arrList[i]) { var v = arrList[i][j]; if (index[v] === undefined) index[v] = 0; index[v]++; }; }; var retv = []; for (var i in index) { if (index[i] == arrLength) retv.push(i); }; return retv; };
он работает только для значений, которые могут быть оценены как строки, и вы должны передать их в виде массива, например:
intersect ([arr1, arr2, arr3...]);
...но он прозрачно принимает объекты как параметр или как любой из элементов, которые будут пересекаться (всегда возвращая массив общих значений). Примеры:
intersect ({foo: [1, 2, 3, 4], bar: {a: 2, j:4}}); // [2, 4] intersect ([{x: "hello", y: "world"}, ["hello", "user"]]); // ["hello"]
EDIT: я просто заметил, что это, в некотором смысле, немного детская коляска.
то есть: я закодировал его, думая, что входные массивы сами по себе не могут содержать повторений (как показано в Примере).
но если входные массивы содержат повторения, это приведет к неправильным результатам. Пример (использование ниже реализации):
intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]); // Expected: [ '1' ] // Actual: [ '1', '3' ]
к счастью, это легко исправить, просто добавив второй индексации уровня. Что это:
изменения:
if (index[v] === undefined) index[v] = 0; index[v]++;
by:
if (index[v] === undefined) index[v] = {}; index[v][i] = true; // Mark as present in i input.
...и:
if (index[i] == arrLength) retv.push(i);
by:
if (Object.keys(index[i]).length == arrLength) retv.push(i);
пример:
// Calculate intersection of multiple array or object values. function intersect (arrList) { var arrLength = Object.keys(arrList).length; // (Also accepts regular objects as input) var index = {}; for (var i in arrList) { for (var j in arrList[i]) { var v = arrList[i][j]; if (index[v] === undefined) index[v] = {}; index[v][i] = true; // Mark as present in i input. }; }; var retv = []; for (var i in index) { if (Object.keys(index[i]).length == arrLength) retv.push(i); }; return retv; }; intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]); // [ '1' ]
Я свой вклад в то, что работает лучше для меня:
if (!Array.prototype.intersect){ Array.prototype.intersect = function (arr1) { var r = [], o = {}, l = this.length, i, v; for (i = 0; i < l; i++) { o[this[i]] = true; } l = arr1.length; for (i = 0; i < l; i++) { v = arr1[i]; if (v in o) { r.push(v); } } return r; }; }
"indexOf" для IE 9.0, chrome, firefox, opera,
function intersection(a,b){ var rs = [], x = a.length; while (x--) b.indexOf(a[x])!=-1 && rs.push(a[x]); return rs.sort(); } intersection([1,2,3], [2,3,4,5]); //Result: [2,3]
'use strict' // Example 1 function intersection(a1, a2) { return a1.filter(x => a2.indexOf(x) > -1) } // Example 2 (prototype function) Array.prototype.intersection = function(arr) { return this.filter(x => arr.indexOf(x) > -1) } const a1 = [1, 2, 3] const a2 = [2, 3, 4, 5] console.log(intersection(a1, a2)) console.log(a1.intersection(a2))
функциональный подход с ES2015
функциональный подход должен рассматривать использование только чистых функций без побочных эффектов, каждый из которых связан только с одной работой.
эти ограничения повышают композиционность и многократное использование задействованных функций.
// small, reusable auxiliary functions const createSet = xs => new Set(xs); const filter = f => xs => xs.filter(apply(f)); const apply = f => x => f(x); // intersection const intersect = xs => ys => { const zs = createSet(ys); return filter(x => zs.has(x) ? true : false ) (xs); }; // mock data const xs = [1,2,2,3,4,5]; const ys = [0,1,2,3,3,3,6,7,8,9]; // run it console.log( intersect(xs) (ys) );
обратите внимание, что уроженца
Set
тип использован, который имеет выгодное производительность поиска.избежать дубликаты
очевидно, неоднократно происходящие элементы с первого
Array
сохраняются, в то время как второйArray
де-дублированные. Это может быть или не быть желательным поведением. Если вам нужен уникальный результат просто применитьdedupe
к первому аргументу:// auxiliary functions const apply = f => x => f(x); const comp = f => g => x => f(g(x)); const afrom = apply(Array.from); const createSet = xs => new Set(xs); const filter = f => xs => xs.filter(apply(f)); // intersection const intersect = xs => ys => { const zs = createSet(ys); return filter(x => zs.has(x) ? true : false ) (xs); }; // de-duplication const dedupe = comp(afrom) (createSet); // mock data const xs = [1,2,2,3,4,5]; const ys = [0,1,2,3,3,3,6,7,8,9]; // unique result console.log( intersect(dedupe(xs)) (ys) );
вычислить пересечение любого числа
Array
sесли вы хотите вычислить пересечение произвольного числа
Array
s просто сочинитьintersect
сfoldl
. Вот функция удобства:// auxiliary functions const apply = f => x => f(x); const uncurry = f => (x, y) => f(x) (y); const createSet = xs => new Set(xs); const filter = f => xs => xs.filter(apply(f)); const foldl = f => acc => xs => xs.reduce(uncurry(f), acc); // intersection const intersect = xs => ys => { const zs = createSet(ys); return filter(x => zs.has(x) ? true : false ) (xs); }; // intersection of an arbitrarily number of Arrays const intersectn = (head, ...tail) => foldl(intersect) (head) (tail); // mock data const xs = [1,2,2,3,4,5]; const ys = [0,1,2,3,3,3,6,7,8,9]; const zs = [0,1,2,3,4,5,6]; // run console.log( intersectn(xs, ys, zs) );
для простоты:
// Usage const intersection = allLists .reduce(intersect, allValues) .reduce(removeDuplicates, []); // Implementation const intersect = (intersection, list) => intersection.filter(item => list.some(x => x === item)); const removeDuplicates = (uniques, item) => uniques.includes(item) ? uniques : uniques.concat(item); // Example Data const somePeople = [bob, doug, jill]; const otherPeople = [sarah, bob, jill]; const morePeople = [jack, jill]; const allPeople = [...somePeople, ...otherPeople, ...morePeople]; const allGroups = [somePeople, otherPeople, morePeople]; // Example Usage const intersection = allGroups .reduce(intersect, allPeople) .reduce(removeDuplicates, []); intersection; // [jill]
преимущества:
- грязь просто
- data-centric
- работает для произвольного количества списков
- работает для произвольных длин списков
- работает для произвольных типов значений
- работает для произвольного порядка сортировки
- сохраняет форму (порядок первого появления в любое время)
- выходит рано, где это возможно
- сейф памяти, за исключением подделки с прототипами функции / массива
недостатки:
- более высокое использование памяти
- более высокая загрузка процессора
- требуется понимание уменьшения
- требуется понимание потока данных
вы не хотели бы использовать это для работы с 3D-движком или ядром, но если у вас есть проблемы с запуском этого приложения на основе событий, у вашего дизайна есть большие проблемы.
.reduce
построить карту, и.filter
найти пересечение.delete
внутри.filter
позволяет нам рассматривать второй массив как уникальный набор.function intersection (a, b) { var seen = a.reduce(function (h, k) { h[k] = true; return h; }, {}); return b.filter(function (k) { var exists = seen[k]; delete seen[k]; return exists; }); }
Я нахожу этот подход довольно легко в понимании. Он работает в постоянном времени.
Это, наверное, самый простой, кроме того list1.фильтр (n = > list2.включает(n))
var list1 = ['bread', 'ice cream', 'cereals', 'strawberry', 'chocolate'] var list2 = ['bread', 'cherry', 'ice cream', 'oats'] function check_common(list1, list2){ list3 = [] for (let i=0; i<list1.length; i++){ for (let j=0; j<list2.length; j++){ if (list1[i] === list2[j]){ list3.push(list1[i]); } } } return list3 } check_common(list1, list2) // ["bread", "ice cream"]
здесь подчеркивания.js реализация:
_.intersection = function(array) { if (array == null) return []; var result = []; var argsLength = arguments.length; for (var i = 0, length = array.length; i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; for (var j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; };
Источник:http://underscorejs.org/docs/underscore.html#section-62
var listA = [1,2,3,4,5,6,7]; var listB = [2,4,6,8]; var result = listA.filter(itemA=> { return listB.some(itemB => itemB === itemA); });
function getIntersection(arr1, arr2){ var result = []; arr1.forEach(function(elem){ arr2.forEach(function(elem2){ if(elem === elem2){ result.push(elem); } }); }); return result; } getIntersection([1,2,3], [2,3,4,5]); // [ 2, 3 ]
вот очень наивная реализация, которую я использую. Он неразрушающий, а также гарантирует, что не дублирует целые.
Array.prototype.contains = function(elem) { return(this.indexOf(elem) > -1); }; Array.prototype.intersect = function( array ) { // this is naive--could use some optimization var result = []; for ( var i = 0; i < this.length; i++ ) { if ( array.contains(this[i]) && !result.contains(this[i]) ) result.push( this[i] ); } return result; }
пересечение N массивов в coffeescript
getIntersection: (arrays) -> if not arrays.length return [] a1 = arrays[0] for a2 in arrays.slice(1) a = (val for val in a1 when val in a2) a1 = a return a1.unique()
я расширил ответ тарулена для работы с любым количеством массивов. Он также должен работать с нецелыми значениями.
function intersect() { const last = arguments.length - 1; var seen={}; var result=[]; for (var i = 0; i < last; i++) { for (var j = 0; j < arguments[i].length; j++) { if (seen[arguments[i][j]]) { seen[arguments[i][j]] += 1; } else if (!i) { seen[arguments[i][j]] = 1; } } } for (var i = 0; i < arguments[last].length; i++) { if ( seen[arguments[last][i]] === last) result.push(arguments[last][i]); } return result; }
основываясь на отличном ответе Анона, этот возвращает пересечение двух или более массивов.
function arrayIntersect(arrayOfArrays) { var arrayCopy = arrayOfArrays.slice(), baseArray = arrayCopy.pop(); return baseArray.filter(function(item) { return arrayCopy.every(function(itemList) { return itemList.indexOf(item) !== -1; }); }); }