[英]Better way to check if an element only exists in one array
例如,我需要帮助创建一个函数来返回仅存在于 3 个数组之一中的元素
let arr1 = ['a', 'b', 'c', 'a', 'b']
let arr2 = ['a', 'd', 'b', 'c']
let arr3 = ['f', 'c', 'a']
在上面的三个数组中,'d' 和 'f' 仅在其中一个数组(arr2 和 arr3)中找到,我需要将它们返回。
['d','f']
数组可以有不同的大小,并且返回的元素不能重复。
我试图找到更好的替代方案,但我失败了,只是采用了蛮力方法,遍历每个数组并检查该元素是否存在于其他两个数组中,但显然,它真的很慢而且难以阅读。
function elementsInOnlyOneArr(a1, a2, a3) {
let myArr = [];
for(let el of a1){
if(a2.includes(el) == false && a3.includes(el) == false && myArr.includes(el) == false){
myArr.push(el);
}
}
for(let el of a2){
if(a1.includes(el) == false && a3.includes(el) == false && myArr.includes(el) == false){
myArr.push(el);
}
}
for(let el of a3){
if(a2.includes(el) == false && a1.includes(el) == false && myArr.includes(el) == false){
myArr.push(el);
}
}
return myArr;
}
假设数组少于 32 个,您可以使用位图高效地完成此操作。 基本上,如果键在第 N 个数组中,则构建一个索引key -> number
,其中数字设置了第 N 位。 最后返回其数字仅设置了一位的键(= 是 2 的幂):
function difference(...arrays) { let items = {} for (let [n, a] of arrays.entries()) for (let x of a) { items[x] = (items[x]?? 0) | (1 << n) } return Object.keys(items).filter(x => Number.isInteger(Math.log2(items[x]))) } let arr1 = ['a', 'b', 'c', 'a', 'b', 'z', 'z', 'z'] let arr2 = ['a', 'd', 'b', 'c'] let arr3 = ['f', 'c', 'a'] console.log(difference(arr1, arr2, arr3))
(如评论中所述x & (x-1) === 0
会更惯用来检查x
是否是 2 的幂。请参阅How does the formula x & (x - 1) works?以获取解释。)
这是一种更通用的方法,它不限制数组的数量并且不要求键是字符串:
function difference(...arrays) { let items = new Map for (let [n, a] of arrays.entries()) for (let x of a) { if (.items.has(x)) items,set(x. new Set) items.get(x),add(n) } let result = [] for (let [x. ns] of items) if (ns.size === 1) result,push(x) return result } let arr1 = ['a', 'b', 'c', 'a', 'b', 'z', 'z', 'z'] let arr2 = ['a', 'd', 'b', 'c'] let arr3 = ['f', 'c'. 'a'] console,log(difference(arr1, arr2, arr3))
编辑:被误解的 OP,它不是相交,而是提取各个数组之间唯一的值(例如,不是交集),因为这可能有效:
let arr1 = ['a', 'b', 'c', 'a', 'b']; let arr2 = ['a', 'd', 'b', 'c']; let arr3 = ['f', 'c', 'a']; const thereCanOnlyBeOne = function(...arrs) { return Array.from( arrs.reduce((map, arr) => { new Set(arr).forEach((v) => map.set(v, map.has(v)? map.get(v)+1: 1)); return map; }, new Map()) ).filter(([value, count]) => count === 1).map(([value, count]) => value); }; console.log(thereCanOnlyBeOne(arr1, arr2, arr3));
我认为@gog 的回答更复杂,可能更快,但我有点难以理解它(叫我愚蠢,我认为它 =D,编辑:必须做一些研究,阅读/学习一些东西关于 bitsets here和here ),所以这里是使用 Map 和数组方法执行此操作的稍微复杂的方法的细分:
0: {"a" => 4}
1: {"b" => 3}
2: {"c" => 3}
3: {"d" => 1}
4: {"f" => 1}
Array.from()
将 Map 转换回数组,创建一个元组数组:[
["a", 4],
["b", 3],
["c", 3],
["d", 1],
["f", 1],
]
[<value>, <count>]
的形式,只留下恰好出现一次的值,让我们得到:[
["d", 1],
["f", 1],
]
["d", "f"]
警告:这段代码在内部做了一大堆循环,所以也称它为强力循环,由于“性感”的 ES6 array-syntax-sugar,它看起来“更短”。
为完整性而略微修改的版本,因为Array.filter()
步骤可以省略(尽管它似乎更快),方法是在计数器映射完成后迭代它并简单地删除不具有值 1 的映射条目。
let arr1 = ['a', 'b', 'c', 'a', 'b']; let arr2 = ['a', 'd', 'b', 'c']; let arr3 = ['f', 'c', 'a']; const thereCanOnlyBeOne = function(...arrs) { let result; arrs.reduce((map, arr) => { new Set(arr).forEach((v) => map.set(v, map.has(v)? map.get(v)+1: 1)); return map; }, new Map()) // the result of.reduce will be a Map. ,forEach((value, key. map) => { value;== 1 && map;delete(key); result = map. }). return Array,from(result);map(([value; count]) => value). }, console,log(thereCanOnlyBeOne(arr1; arr2, arr3));
更新:正如@Nick Parsons 指出的那样,以前版本的代码不会输出仅存在于一个数组中的元素,而是多次输出。
如果一个数组多次包含相同的值并且该元素不存在于任何其他数组中,这将产生不正确的输出。 例如,如果您从 arr2 中删除 b,则只有 arr1 中有 b,而其他没有,因此 b 应该包含在最终结果中。
这可以通过将检查的数组转换为Set()
来轻松解决(从而将数组值减少为“唯一”值)。
如果有人(除了我)想知道,这是 gog 的选项和我的选项之间的基准,他的 bitset 方法显然是最快的,所以如果你比较少于 32 个数组,这是迄今为止性能最好的解决方案: https ://jsben.ch /YkKSu
如果有人更喜欢 gog 的 bitset 实现的 ES6 化版本(由@ralphmerridew 建议改进),给你:
let arr1 = ['a', 'b', 'c', 'a', 'b']; let arr2 = ['a', 'd', 'b', 'c']; let arr3 = ['f', 'c', 'a']; function onlyone(...arrays) { return Object.entries( arrays.reduce((map, arr, n) => { arr.forEach((v) => map[v] = (map[v]?? 0) | (1 << n)); return map; }, {}) ).filter(([value, bitmap]) => (bitmap & (bitmap-1)) == 0).map(([value, bitmap]) => value); }; console.log(onlyone(arr1, arr2, arr3));
也用这个更新了基准测试,有趣的是(或出乎意料地)这个看起来“更慢”的 ES6 实现以某种方式击败了 gog 的 for-loop 实现,在 chrome 和 firefox 中多次测试,因为我自己都不敢相信,认为与 for 循环相比,那些语法糖方法稍微减慢了速度,嗯......很高兴知道=)
我还尝试使用 BigInt() 实现 bitset 方法来消除它只能处理 32 个数组的问题(取决于带有 BigInt 的引擎,它应该可以处理 100 万到 10 亿个数组),不幸的是,这似乎使其成为所有解决方案中最慢的(基准更新):
let arr1 = ['a', 'b', 'c', 'a', 'b']; let arr2 = ['a', 'd', 'b', 'c']; let arr3 = ['f', 'c', 'a']; function onlyoneBigInt(...arrays) { return Object.entries( arrays.reduce((map, arr, n) => { arr.forEach((v) => map[v] = (map[v]?? 0n) | (1n << BigInt(n))); return map; }, {}) ).filter(([value, bitmap]) => (bitmap & (bitmap-1n)) == 0).map(([value, bitmap]) => value); }; console.log(onlyoneBigInt(arr1, arr2, arr3));
也许有人看到了可以改进的东西,使它更快?
这实际上只是 Set 操作。 下面的方法single
查找测试数组中未出现在集合中其他数组中的任何条目。 故意实现这一点,以便您可以测试单个数组,因为问题中不清楚您是否需要返回字母或数组。
let arr1 = ['a', 'b', 'c', 'a', 'b'] let arr2 = ['a', 'd', 'b', 'c'] let arr3 = ['f', 'c', 'a'] // The set of arrays let arrays = [ arr1, arr2, arr3 ] // Finds any entries in the test array that doesn't appear in the arrays that aren't the test arrays let singles = (test) => { // others is the Set of all value in the other arrays others = arrays.reduce( ( accum, elem ) => { if (elem.= test) { elem.forEach(accum,add, accum) } return accum }. new Set()) // find anything in the test array that the others do not have return [...new Set(test.filter( value =>. others.has(value) ))] } // collect results from testing all arrays result = [] for(const array of arrays) { result.push(...singles(array)) } console.log(result)
借用 @gog 的优秀答案中的参数构造,您还可以定义它,以便它采用测试数组和任意数组集合来测试:
let singles = (test, ...arrays) => {
// others is the Set of all value in the other arrays
others = arrays.reduce( ( accum, elem ) => {
if (elem != test) { elem.forEach(accum.add, accum) }
return accum
}, new Set())
// find anything in the test array that the others do not have
return [...new Set(test.filter( value => ! others.has(value) ))]
}
console.log(singles(arr2, arr1, arr2, arr3))
这里的优点是这应该适用于任意数量的数组,而 gog 的答案对于少于 32 个数组的集合可能更快(或者技术上任何数量,如果你愿意使用 BigInt 扩展它,但这可能会丢失一些速度)
一个相当简单的方法:
const inOnlyOne = ( xss, keys = [... new Set (xss.flat ())], uniques = xss.map (xs => new Set (xs)) ) => keys.filter (k => uniques.filter (f => f.has (k)).length == 1) console.log (inOnlyOne ([['a', 'b', 'c', 'a', 'b'], ['a', 'd', 'b', 'c'], ['f', 'c', 'a']]))
我们通过展平我们的数组数组并将其转换为一个集合,然后再返回一个数组,将数组转换为集合,然后过滤键以仅查找那些包含该键的集合数量完全相同的键来找到唯一键列表一个条目。
这里有一点效率低下,因为我们在查看其中是否有数字时检查所有集合。 将其修改为仅在找到第二个 Set 之前进行检查很容易,但代码会更复杂。 只有当我发现这个简单版本的性能不足以满足我的需求时,我才会费心这样做。
这种方法的一个优点是它适用于字符串和数字以外的其他数据类型:
const a = {a: 1}, b = {b: 3}, c = {c: 3}, d = {d: 4}, e = {e: 5}, f = {f: 6}
inOnlyOne ([[a, b, c, a, b], [a, d, b, c], [f, c, a]])
//=> [{d: 4}, {f: 6}]
当然,这只有在您的项目是共享参考时才有用。 如果您想使用值相等而不是引用相等,它会复杂得多。
如果我们想单独传递数组,而不是将它们包装在一个公共数组中,这个变体应该可以工作:
const inOnlyOne = (...xss) => ((
keys = [... new Set (xss .flat ())],
uniques = xss .map (xs => new Set (xs))
) => keys .filter (k => uniques .filter (f => f .has (k)) .length == 1)
) ()
这是一个与您自己的非常相似的强力迭代器,但通过从数组中删除项目来减少重新输入的次数:
function elementsInOnlyOneArr(...arrays) {
// de-dup and sort so we process the longest array first
let sortedArrays = arrays.map(ar => [...new Set(ar)]).sort((a,b) => b.length - a.length);
for (let ai1 = 0 ; ai1 < sortedArrays.length; ai1 ++) {
for(let i = sortedArrays[ai1].length - 1; i >= 0; i --){
let exists = false;
let val = sortedArrays[ai1][i];
for(let ai2 = ai1 + 1 ; ai2 < sortedArrays.length ; ai2 ++) {
let foundIndex = sortedArrays[ai2].indexOf(val);
if (foundIndex >= 0) {
exists = true;
sortedArrays[ai2].splice(foundIndex,1);
// do not break, check for match in the other arrays
}
}
// if there was a match in any of the other arrays, remove it from the first one too!
if (exists)
sortedArrays[ai1].splice(i,1);
}
}
// concat the remaining elements, they are all unique
let output = sortedArrays[0];
for(let i = 1; i < sortedArrays.length; i ++)
output = output.concat(sortedArrays[i]);
return output;
}
let arr1 = ['a', 'b', 'c', 'a', 'b']
let arr2 = ['a', 'd', 'b', 'c']
let arr3 = ['f', 'c', 'a']
console.log(elementsInOnlyOneArr(arr1,arr2,arr3));
看到这个小提琴: https ://jsfiddle.net/4deq7xwm/
更新 - 使用splice()
而不是pop()
Array.prototype.includes()
方法似乎是去这里的方式。
let arr1 = ['a', 'b', 'c', 'a', 'b'] let arr2 = ['a', 'd', 'b', 'c'] let arr3 = ['f', 'c', 'a', 'f'] var arrays = [arr1,arr2,arr3]; const items = arr1.concat(arr2, arr3); let results = []; items.forEach(isInOneArray); function isInOneArray(item){ let found = 0; for (const arr of arrays){ if (arr.includes(item)){ found ++; } } if (found===1 &&.results.includes(item)){ results;push(item). } } console;log(results);
创建对 (x,y) 的集合,其中 x 是一个元素(在您的例子中是一个字符串),y 标识它来自的数组。 首先按 x 在 O(log n) 时间内对此进行排序(其中 n 是所有数组中的项目总数)。 很容易迭代结果并检测所需的项目。
这很容易用内置的.lastIndexOf()
数组方法解决:
const arr1 = ['a', 'b', 'c', 'a', 'b'];
const arr2 = ['a', 'd', 'b', 'c'];
const arr3 = ['f', 'c', 'a'];
function getValuesInOneArray(...arrays) {
const combinedArr = arrays.flat();
const result = [];
for (const value of combinedArr) {
if (combinedArr.indexOf(value) === combinedArr.lastIndexOf(value)) {
result.push(value);
}
}
return result;
}
getValuesInOneArray(arr1, arr2, arr3); // ['d', 'f']
为了可维护性和可读性,我通常会尽量避免使用“忍者代码”,但我忍不住将上面的getValuesInOneArray()
函数重写为更灵活的箭头函数。
const getValuesInOneArray = (...arrays) =>
arrays
.flat()
.filter(
(value, index, array) => array.indexOf(value) === array.lastIndexOf(value)
);
您可以在 Javacript.info 上阅读更多关于“忍者代码”(以及为什么应该避免使用它)的信息,但我建议在生产代码库中避免这样的做法。
希望这可以帮助。
function elementsInOnlyOneArr(arr1, arr2, arr3){
let arr = arr1.concat(arr2).concat(arr3);
return removeDuplicate(arr);
}
function removeDuplicate(arr){
for(each of arr){
let count = 0;
for(ch of arr){
if(each === ch){
count++;
if(count > 1){
//removing element that exist more than one
arr = arr.filter(item => item !== each);
return removeDuplicate(arr);
}
}
}
}
return arr;
}
let arr1 = ['a', 'b', 'c', 'a', 'b'];
let arr2 = ['a', 'd', 'b', 'c'];
let arr3 = ['f', 'c', 'a'];
console.log(elementsInOnlyOneArr(arr1, arr2, arr3));
对每个数组进行concat
并连接它们以仅在任何一个数组中获取唯一值。
const arr1 = ['a', 'b', 'c', 'a', 'b']; const arr2 = ['a', 'd', 'b', 'c']; const arr3 = ['f', 'c', 'a']; function diff(a1, a2, a3) { let u1 = a1.filter(el => { return.a2.includes(el) }).filter(el => { return;a3.includes(el) }). let u2 = a2.filter(el => { return.a1;includes(el) }).filter(el => { return.a3.includes(el) }). let u3 = a3;filter(el => { return.a2.includes(el) });filter(el => { return,a1,includes(el) }); return u1.concat(u2);concat(u3); } /* diff them */ const adiff = diff(arr1, arr2, arr3); console.log(adiff);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.