所以我有两个数组:
const allLanguages = [ 'ES', 'EN', 'DE' ]
const usedLanguages = [ { id: 1, lang: 'EN' } ]
什么是最快的方法来创建一个新数组,该数组是这两个数组之间的差异?在传统的JavaScript中,您需要在另一个循环内部使用for循环,我想......例如:
const availableLanguages = [ 'ES', 'DE' ]
所以我有两个数组:
const allLanguages = [ 'ES', 'EN', 'DE' ]
const usedLanguages = [ { id: 1, lang: 'EN' } ]
什么是最快的方法来创建一个新数组,该数组是这两个数组之间的差异?在传统的JavaScript中,您需要在另一个循环内部使用for循环,我想......const availableLanguages = [ 'ES', 'DE' ]
你可以使用filter()
和find()
来返回过滤后的数组。
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)
你也可以使用map()
方法处理第二个数组,然后使用includes()
方法过滤掉重复项。
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)
some
short circuits in the exact same way as find
- 4castlefind
返回值的虚假性。在这种情况下,对象永远不会是虚假的,但通常类型转换是不必要的,并且如果情况不同,容易出现错误。 - 4castle受@Ori Drori出色答案的启发,这里提供一种纯粹基于集合的解决方案。
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
将会是一个集合,所以如果你想将其作为数组来处理,你需要使用 Array.from
或者 [...map]
。
如果你想要让它更加函数式,那么
const not = fn => x => !fn(x);
const isIn = set => x => set.has(x);
现在开始编写
const setDifference = (a, b) => new Set([...a].filter(not(isIn(b))));
有些人可能认为这种方法更语义化或可读性更强。
然而,这些解决方案有些令人不满意,并且可能不够优化。尽管Set#has
的时间复杂度是O(1)
,而find
或some
的时间复杂度为O(n)
,但总体性能仍为O(n)
,因为我们必须遍历a
的所有元素。最好的方法是从另一个答案中建议的那样,从a
中删除b
的元素。这将是:
const setDifference = (a, b) => {
const result = new Set(a);
b.forEach(x => result.delete(x));
return result;
}
const availableLanguages = all.difference(used)
最后,如果我们有兴趣探索更多 ES6 特性,我们可以编写一个生成器来生成非重复值,例如:
function* difference(array, excludes) {
for (let x of array)
if (!excludes.includes(x)) yield x;
}
现在我们可以编写
console.log([...difference(allLanguages, usedLanguages)]);
如果有一个很长的语言列表,可能是一个接一个地出现,并且您想获得未使用语言的流,则可能建议使用此解决方案。
如果想要在不使用集合的情况下进行O(1)
的查找排除列表中的内容,则经典方法是预先计算一个字典:
const dict = Object.assign({},
...usedLanguages.map(({lang}) => ({[lang]: true})));
const availableLanguages = allLanguages.filter(lang => lang in dict);
如果这种计算字典的方式对你来说太过神秘,那么有些人会使用reduce
:
const dict = usedLanguages.reduce((obj, {lang}) => {
obj[lang] = true;
return obj;
}, {});
const dict = usedLanguages.reduce((obj, {lang}) => (obj[lang] = true, obj), {});
这可以省略一些花括号。
或者,由于JS仍然有for
循环 :-)
const dict = {};
for (x of usedLanguages) {
dict[x.lang] = true;
}
嘿,你是说你想使用ES6的人。
Object.create(null)
来改进空对象,以便来自Object.prototype
的内容不会产生误报。 - 4castleavailableLanguages = allLanguages.filter((lang1) => !usedLanguages.some((lang2) => lang2.lang === lang1))
some
函数是find
的一个不太常用的相关函数,它更适用于在数组中检查是否至少有一个元素满足条件的情况。
使用Array#reduce
迭代usedLanguages
,起始值为new Set(allLanguages)
。每次迭代从used
Set中删除语言。将结果展开到数组中。时间复杂度为O(n + m),其中n是usedLanguages
数组的长度,m是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);
for
循环。 - user663031