Get the difference of arrays in ES6?
You can use filter()
and find()
to return filtered array.
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)
You could also map()
second array and then use includes()
to filter out duplicates.
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)
Set-based approach
Inspired by @Ori Drori's excellent answer, here is a pure set-based solution.
const all = new Set(allLanguages);
const used = new Set(usedLanguages.map(({lang}) => lang));
const availableLanguages = setDifference(all, used);
where
const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));
availableLanguages
will be a set, so to work with it as an array you'll need to do Array.from
or [...map]
on it.
If one wanted to get all functional, then
const not = fn => x => !fn(x);
const isIn = set => x => set.has(x);
Now write
const setDifference = (a, b) => new Set([...a].filter(not(isIn(b))));
which some might consider more semantic or readable.
However, these solutions are somewhat unsatisfying and could be suboptimal. Even though Set#has
is O(1)
, compared to O(n)
for find
or some
, the overall performance is still O(n)
, since we have to iterate through all the elements of a
. It would be better to delete the elements from b
from a
, as was suggested in another answer. This would be
const setDifference = (a, b) => {
const result = new Set(a);
b.forEach(x => result.delete(x));
return result;
}
We can't use reduce
since that is not available on sets, and we don't want to have to convert the set into an array to use it. But we can use forEach
, which is available on sets. This alternative would be preferable if a
is larger and b
is smaller.
Most likely some future version of JS will have this built-in, allowing you to just say
const availableLanguages = all.difference(used)
Generator-based approach
Finally, if we are interested in exploring more ES6 features, we could write this as a generator which generates non-duplicate values, as in
function* difference(array, excludes) {
for (let x of array)
if (!excludes.includes(x)) yield x;
}
Now we can write
console.log([...difference(allLanguages, usedLanguages)]);
This solution might be recommended if there was a long list of languages, perhaps coming in one by one, and you wanted to get a stream of the non-used ones.
Using a dictionary
If one wanted O(1)
lookup into the list of exclusions without using sets, the classic approach is to pre-calculate a dictionary:
const dict = Object.assign({},
...usedLanguages.map(({lang}) => ({[lang]: true})));
const availableLanguages = allLanguages.filter(lang => lang in dict);
If that way of computing the dictionary is too arcane for you, then some people use reduce
:
const dict = usedLanguages.reduce((obj, {lang}) => {
obj[lang] = true;
return obj;
}, {});
Nina likes to write this using the comma operator as
const dict = usedLanguages.reduce((obj, {lang}) => (obj[lang] = true, obj), {});
which saves some curly braces.
Or, since JS still has for
loops :-):
const dict = {};
for (x of usedLanguages) {
dict[x.lang] = true;
}
Hey, you're the one who said you want to use ES6.
You can use the following code:
availableLanguages = allLanguages.filter((lang1) => !usedLanguages.some((lang2) => lang2.lang === lang1))
The some
function is a lesser known relative to find
that is better suited for cases where you want to check if a condition is met by at least one element in the array.