Code le plus simple pour l'intersection des tableaux en javascript
Quel est le code le plus simple, sans bibliothèque pour implémenter des intersections de tableaux dans javascript? Je veux écrire
intersection([1,2,3], [2,3,4,5])
et obtenir
[2, 3]
30 réponses
utiliser une combinaison de Array.prototype.filter
et Array.prototype.indexOf
:
array1.filter(value => -1 !== array2.indexOf(value));
Destructive semble le plus simple, surtout si nous pouvons supposer que l'entrée est triée:
/* 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;
}
Non destructif doit être un cheveu plus compliqué, car nous avons à suivre les indices:
/* 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;
}
si votre environnement supporte ECMAScript 6 Set , un moyen simple et soi-disant efficace (voir lien spécification):
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);
}
plus court, mais moins lisible (également sans créer l'intersection supplémentaire Set
):
function intersect(a, b) {
return [...new Set(a)].filter(x => new Set(b).has(x));
}
notez que l'implémentation de l'ensemble ne permettra que des valeurs uniques, donc new Set[1,2,3,3].size
évalue à 3
.
Utilisant Underscore.js
_.intersection( [0,345,324] , [1,0,324] ) // gives [0,324]
pourquoi ne pas utiliser des tableaux associatifs?
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;
}
ma contribution en termes ES6. En général, il trouve l'intersection d'un tableau avec le nombre indéfini de tableaux fournis comme arguments.
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>");
utilisant jQuery :
var a = [1,2,3];
var b = [2,3,4,5];
var c = $(b).not($(b).not(a));
alert(c);
la performance de l'implémentation de @atk pour les tableaux triés de primitives peut être améliorée en utilisant .pop plutôt que .changement.
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;
}
j'ai créé un benchmark en utilisant jsPerf: http://bit.ly/P9FrZK . Il est environ trois fois plus rapide à utiliser .pop.
- Trier
- cochez un par un à partir de l'index 0, créez un nouveau tableau à partir de cela.
quelque chose comme ça, pas bien testé cependant.
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: l'algorithme prévu uniquement pour les nombres et les chaînes normales, intersection de tableaux d'objets arbitaires peut ne pas fonctionner.
pour les tableaux ne contenant que des chaînes ou des nombres, vous pouvez faire quelque chose avec le tri, selon certaines des autres réponses. Pour le cas général de tableaux d'objets arbitraires je ne pense pas que vous pouvez éviter de le faire le long chemin. Ce qui suit vous donnera l'intersection de n'importe quel nombre de tableaux fournis comme paramètres à 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]));
je recommande ci-dessus solution succincte qui surpasse les autres mises en œuvre sur de grandes entrées. Si la performance sur de petites entrées importe, vérifiez les alternatives ci-dessous.
solutions de rechange et de comparaison des performances:
voir l'extrait suivant pour les implémentations alternatives et cocher https://jsperf.com/array-intersection-comparison pour comparaison des performances.
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;
}
résultats dans Firefox 53:
-
Ops/sec sur les grands réseaux (10 000 éléments):
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 sur les petits réseaux (100 éléments):
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
c'est assez court en utilisant ES2015 et Sets. Accepte les valeurs de type tableau comme une chaîne de caractères et supprime les doublons.
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(''));
un petit ajustement au plus petit ici(le filtre/solution d'indexOf ), à savoir la création d'un index des valeurs dans l'un des tableaux en utilisant un objet JavaScript, le réduira de O (N*M) au temps linéaire "probablement". source1 source2
function intersect(a, b) {
var aa = {};
a.forEach(function(v) { aa[v]=1; });
return b.filter(function(v) { return v in aa; });
}
ce n'est pas la solution la plus simple (c'est plus de code que filtre+index de ), ni la plus rapide (probablement plus lent d'un facteur constant que intersect_safe() ), mais semble être un assez bon équilibre. Il est sur le très côté simple, tout en fournissant de bonnes performances, et il ne nécessite pas d'entrées pré-triées.
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;
}
avec quelques restrictions sur vos données, vous pouvez le faire dans linéaire time!
pour entiers positifs : utilisez un tableau cartographiant les valeurs à un booléen "vu/pas vu".
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;
}
il existe une technique similaire pour objets : prenez une clé factice, mettez-la à" true " pour chaque élément du tableau 1, puis cherchez cette clé dans elements of array2. Nettoyer lorsque vous avez terminé.
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;
}
bien sûr, vous devez vous assurer que la clé n'est pas apparue avant, sinon vous détruirez vos données...
une autre approche indexée capable de traiter n'importe quel nombre de tableaux à la fois:
// 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;
};
cela ne fonctionne que pour les valeurs qui peuvent être évaluées comme des chaînes et vous devriez les passer comme un tableau comme:
intersect ([arr1, arr2, arr3...]);
...mais il accepte de manière transparente les objets comme paramètre ou comme l'un des éléments à intersecter (en renvoyant toujours un tableau de valeurs communes). Exemples:
intersect ({foo: [1, 2, 3, 4], bar: {a: 2, j:4}}); // [2, 4]
intersect ([{x: "hello", y: "world"}, ["hello", "user"]]); // ["hello"]
EDIT: J'ai juste remarqué que c'est, d'une certaine manière, légèrement bogué.
C'est-à-dire: je l'ai codé en pensant que les tableaux d'entrées ne peuvent pas eux-mêmes contenir des répétitions (comme l'exemple fourni ne le fait pas).
mais si les tableaux d'entrée contiennent des répétitions, cela produirait de mauvais résultats. Exemple (en utilisant l'implémentation ci-dessous):
intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]);
// Expected: [ '1' ]
// Actual: [ '1', '3' ]
heureusement, il est facile de corriger cela en ajoutant simplement l'indexation de deuxième niveau. C'est-à-dire:
Changement:
if (index[v] === undefined) index[v] = 0;
index[v]++;
par:
if (index[v] === undefined) index[v] = {};
index[v][i] = true; // Mark as present in i input.
...et:
if (index[i] == arrLength) retv.push(i);
par:
if (Object.keys(index[i]).length == arrLength) retv.push(i);
exemple complet:
// 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' ]
je vais contribuer avec ce qui a fonctionné le mieux pour moi:
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" pour 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))
une approche fonctionnelle avec ES2015
une approche fonctionnelle doit envisager d'utiliser uniquement des fonctions pures sans effets secondaires, dont chacune ne concerne qu'un seul emploi.
ces restrictions améliorent la composabilité et la réutilisation des fonctions concernées.
// 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) );
veuillez noter que le type natif Set
est utilisé, ce qui a un avantage
Lookup performance.
éviter les doublons
de toute évidence, les articles récurrents du premier Array
sont conservés, tandis que le second Array
est dé-dupliqué. Ceci peut être ou ne pas être le comportement désiré. Si vous avez besoin d'un résultat unique il suffit d'appliquer dedupe
au premier argument:
// 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) );
calculer l'intersection de n'importe quel nombre de Array
s
si vous voulez calculer l'intersection d'un nombre arbitraire de Array
s composez juste intersect
avec foldl
. Voici une fonction de commodité:
// 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) );
pour la simplicité:
// 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]
prestations:
- saleté simple
- data-centric
- travaille pour nombre arbitraire de listes
- fonctionne pour les longueurs arbitraires des listes
- travaille pour arbitraire types de valeurs
- travaille pour arbitraire de l'ordre de tri
- conserve la forme (ordre de première apparition dans toute rangée))
- sort le plus tôt possible
- mémoire coffre-fort, court de l'altération de la Fonction / Tableau des prototypes
Inconvénients:
- de plus en plus l'utilisation de la mémoire
- plus grande utilisation CPU
- nécessite une compréhension de réduire
- exige la compréhension du flux de données
vous ne voudriez pas utiliser ceci pour la 3D le moteur ou le noyau fonctionne, mais si vous avez des problèmes pour le faire fonctionner dans une application basée sur un événement, votre conception a de plus gros problèmes.
.reduce
pour construire une carte, et .filter
pour trouver l'intersection. delete
dans le .filter
nous permet de traiter le second tableau comme s'il s'agissait d'un ensemble unique.
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;
});
}
je trouve cette approche assez facile à comprendre. Il se produit en temps constant.
C'est probablement le plus simple, en plus de list1.filtre(n => liste2.comprend(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"]
Voici underscore.js mise en œuvre:
_.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;
};
Source: 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 ]
Voici une implémentation très naïve que j'utilise. Il est non destructif et s'assure également de ne pas dupliquer des entires.
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;
}
intersection de n tableaux en 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()
j'ai étendu la réponse de tarulen à travailler avec n'importe quel nombre de tableaux. Il devrait aussi fonctionner avec des valeurs non-entières.
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;
}
S'appuyant sur L'excellente réponse d'Anon, celle-ci renvoie l'intersection de deux ou plusieurs tableaux.
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;
});
});
}