web-dev-qa-db-fra.com

Quelle est la façon la plus rapide ou la plus élégante de calculer une différence définie à l'aide de tableaux Javascript?

Soit A et B deux ensembles. Je recherche vraiment des moyens rapides ou élégants pour calculer la différence définie (A - B ou A \B, selon votre préférence) entre les deux. Les deux ensembles sont stockés et manipulés sous forme de tableaux Javascript, comme le dit le titre.

Remarques:

  • Les astuces spécifiques à Gecko sont correctes
  • Je préfère m'en tenir aux fonctions natives (mais je suis ouvert à une bibliothèque légère si c'est beaucoup plus rapide)
  • J'ai vu, mais pas testé, JS.Set (voir point précédent)

Edit: J'ai remarqué un commentaire sur les ensembles contenant des éléments en double. Quand je dis "set", je me réfère à la définition mathématique, ce qui signifie (entre autres) qu'ils ne contiennent pas d'éléments en double.

86
Matt Ball

si je ne sais pas si c'est le plus efficace, mais peut-être le plus court

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(function(x) { return B.indexOf(x) < 0 })

console.log(diff);

Mise à jour vers ES6:

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(x => !B.includes(x) );

console.log(diff);
156
user187291

Eh bien, 7 ans plus tard, avec Set de ES6 objet, c'est assez facile (mais toujours pas aussi compact que les pythons A - B), et apparemment plus rapide que indexOf pour les grands tableaux:

console.clear();
let a = new Set([1, 2, 3, 4]);
let b = new Set([5, 4, 3, 2]);


let a_minus_b = new Set([...a].filter(x => !b.has(x)));
let b_minus_a = new Set([...b].filter(x => !a.has(x)));
let a_intersect_b = new Set([...a].filter(x => b.has(x))); 

console.log([...a_minus_b]) // {1}
console.log([...b_minus_a]) // {5}
console.log([...a_intersect_b]) // {2,3,4}
62
milan

Vous pouvez utiliser un objet comme carte pour éviter de numériser linéairement B pour chaque élément de A comme dans réponse de user187291 :

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

La méthode non standard toSource() est utilisée pour obtenir des noms de propriété uniques; si tous les éléments ont déjà des représentations de chaîne uniques (comme c'est le cas avec les nombres), vous pouvez accélérer le code en supprimant les invocations toSource().

15
Christoph

Le plus court, utilisant jQuery, est:

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>
9
perhelion

Je hacherais le tableau B, puis garderais les valeurs du tableau A non présentes dans 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;
}
6
Eric Bréchemier

En incorporant l'idée de Christoph et en supposant quelques méthodes d'itération non standard sur les tableaux et les objets/hachages (each et amis), nous pouvons obtenir la différence, l'union et l'intersection en temps linéaire en environ 20 lignes au total:

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

Cela suppose que each et filter sont définis pour les tableaux, et que nous avons deux méthodes utilitaires:

  • myUtils.keys(hash): retourne un tableau avec les clés du hachage

  • myUtils.select(hash, fnSelector, fnEvaluator): renvoie un tableau avec les résultats de l'appel de fnEvaluator sur les paires clé/valeur pour lesquelles fnSelector renvoie true.

La select() est vaguement inspirée de Common LISP, et est simplement filter() et map() réunies en une seule. (Il serait préférable de les définir sur Object.prototype, Mais cela fait des ravages avec jQuery, alors je me suis contenté de méthodes d'utilité statiques.)

Performance: test avec

var a = [], b = [];
for (var i = 100000; i--; ) {
  if (i % 2 !== 0) a.Push(i);
  if (i % 3 !== 0) b.Push(i);
}

donne deux ensembles de 50 000 et 66 666 éléments. Avec ces valeurs, A-B prend environ 75 ms, tandis que l'union et l'intersection sont d'environ 150 ms chacune. (Mac Safari 4.0, utilisant la date Javascript pour le chronométrage.)

Je pense que c'est un gain décent pour 20 lignes de code.

4
j-g-faustus

Utilisation de nderscore.js (Bibliothèque pour JS fonctionnel)

>>> var foo = [1,2,3]
>>> var bar = [1,2,4]
>>> _.difference(foo, bar);
[4]
3
chribsen

Quant à la façon de jeûner, ce n'est pas si élégant mais j'ai effectué quelques tests pour en être sûr. Le chargement d'un tableau en tant qu'objet est beaucoup plus rapide à traiter en grande quantité:

var t, a, b, c, objA;

    // 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();
objA = {};
a.forEach(function(v) { objA[v] = true; });
c = b.filter(function(v) { return !objA[v]; });
console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);

Résultats:

completed indexOf in 1219 ms with result 5000 length
completed Object in 8 ms with result 5000 length

Cependant, cela fonctionne avec chaînes uniquement. Si vous prévoyez de comparer des ensembles numérotés, vous voudrez mapper les résultats avec parseFloat.

2
SmujMaiku

Quelques fonctions simples, empruntées à la réponse de @ 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]);

Usage:

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 }
2
Brian Burns

Cela fonctionne, mais je pense qu'un autre est beaucoup plus court et élégant aussi

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;
1
Xavi Ivars