web-dev-qa-db-fra.com

Obtenez la différence des tableaux dans ES6?

J'ai donc deux tableaux:

const allLanguages = [ 'ES', 'EN', 'DE' ]
const usedLanguages = [ { id: 1, lang: 'EN' } ]

Quelle est la façon la plus rapide de produire un nouveau tableau qui fait la différence entre ces deux? En JavaScript old school, il faudrait faire une boucle for à l'intérieur d'une autre boucle for, je pense ...

Par exemple:

const availableLanguages = [ 'ES', 'DE' ]
14
j_d

Vous pouvez utiliser filter() et find() pour renvoyer un tableau filtré.

const allLanguages = [ 'ES', 'EN', 'DE' ]
const usedLanguages = [ { id: 1, lang: 'EN' } ]

var result = allLanguages.filter(e => !usedLanguages.find(a => e == a.lang));
console.log(result)

Vous pouvez également map() deuxième tableau, puis utiliser includes() pour filtrer les doublons.

const allLanguages = [ 'ES', 'EN', 'DE' ]
const usedLanguages = [ { id: 1, lang: 'EN' } ].map(e => e.lang);

var result = allLanguages.filter(e => !usedLanguages.includes(e));
console.log(result)
24
Nenad Vracar

Approche basée sur les ensembles

Inspiré par l'excellente réponse de @Ori Drori, voici une pure solution basée sur un ensemble.

const all = new Set(allLanguages);
const used = new Set(usedLanguages.map(({lang}) => lang));

const availableLanguages = setDifference(all, used);

const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));

availableLanguages sera un ensemble, donc pour travailler avec lui en tant que tableau, vous devrez faire Array.from ou [...map] dessus.

Si l'on voulait que tout soit fonctionnel, alors

const not = fn => x => !fn(x);
const isIn = set => x => set.has(x);

Maintenant écris

const setDifference = (a, b) => new Set([...a].filter(not(isIn(b))));

que certains pourraient considérer comme plus sémantique ou lisible.

Cependant, ces solutions sont quelque peu insatisfaisantes et pourraient être sous-optimales. Même si Set#has Est O(1), comparé à O(n) pour find ou some, les performances globales sont toujours O(n), car nous devons parcourir tous les éléments de a. Il serait préférable de supprimer les éléments de b de a, comme cela a été suggéré dans une autre réponse. Ce serait

const setDifference = (a, b) => {
  const result = new Set(a);
  b.forEach(x => result.delete(x));
  return result;
}

Nous ne pouvons pas utiliser reduce car cela n'est pas disponible sur les ensembles, et nous ne voulons pas avoir à convertir l'ensemble en tableau pour l'utiliser. Mais nous pouvons utiliser forEach, qui est disponible sur les ensembles. Cette alternative serait préférable si a est plus grand et b est plus petit.

Très probablement, une future version de JS aura cette fonction intégrée, vous permettant de dire simplement

const availableLanguages = all.difference(used)

Approche basée sur le générateur

Enfin, si nous souhaitons explorer plus de fonctionnalités ES6, nous pourrions l'écrire comme un générateur qui génère des valeurs non dupliquées, comme dans

function* difference(array, excludes) {
  for (let x of array) 
    if (!excludes.includes(x)) yield x;
}

Maintenant on peut écrire

console.log([...difference(allLanguages, usedLanguages)]);

Cette solution pourrait être recommandée s'il y avait une longue liste de langues, peut-être venant une par une, et que vous vouliez obtenir un flux de celles non utilisées.

Utiliser un dictionnaire

Si l'on voulait O(1) rechercher dans la liste des exclusions sans utiliser d'ensembles, l'approche classique est de pré-calculer un dictionnaire:

const dict = Object.assign({}, 
    ...usedLanguages.map(({lang}) => ({[lang]: true})));

const availableLanguages = allLanguages.filter(lang => lang in dict);

Si cette façon de calculer le dictionnaire est trop mystérieuse pour vous, alors certaines personnes utilisent reduce:

const dict = usedLanguages.reduce((obj, {lang}) => {
  obj[lang] = true;
  return obj;
}, {});

Nina aime écrire ceci en utilisant l'opérateur virgule comme

const dict = usedLanguages.reduce((obj, {lang}) => (obj[lang] = true, obj), {});

ce qui permet d'économiser quelques accolades.

Ou, puisque JS a encore for boucles :-):

const dict = {};
for (x of usedLanguages) {
  dict[x.lang] = true;
}

Hé, c'est toi qui a dit que tu voulais utiliser ES6.

9
user663031

Vous pouvez utiliser le code suivant:

availableLanguages = allLanguages.filter((lang1) => !usedLanguages.some((lang2) => lang2.lang === lang1))

La fonction some est une fonction moins connue que find qui convient mieux aux cas où vous souhaitez vérifier si une condition est remplie par au moins un élément du tableau.

6
Shai

Itérer le usedLanguages en utilisant Array#reduce, Avec new Set(allLanguages) comme valeur de départ. Supprimez la langue de used Définissez à chaque itération. Répartissez le résultat dans le tableau. La complexité est O (n + m), où n est la longueur du tableau usedLanguages et m la longueur de allLanguages:

const allLanguages = [ 'ES', 'EN', 'DE' ];
const usedLanguages = [{ id: 1, lang: 'EN' }];

const result = [...usedLanguages.reduce((r, { lang }) => {
  r.delete(lang);
  return r;
}, new Set(allLanguages))];

console.log(result);
3
Ori Drori