kil*_*lex 29 javascript arrays algorithm
例如,我需要帮助创建一个函数来返回仅出现在 3 个数组之一中的元素
let arr1 = ['a', 'b', 'c', 'a', 'b']
let arr2 = ['a', 'd', 'b', 'c']
let arr3 = ['f', 'c', 'a']
Run Code Online (Sandbox Code Playgroud)
在上面的三个数组中,“d”和“f”仅在其中一个数组(arr2 和 arr3)中找到,我需要返回它们。
['d','f']
Run Code Online (Sandbox Code Playgroud)
数组的大小可以不同,并且返回的元素不能重复。
我试图找到更好的替代方案,但失败了,只是采用了暴力方法,循环遍历每个数组并检查该元素是否存在于其他两个数组中,但显然,它非常慢且难以读取。
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;
}
Run Code Online (Sandbox Code Playgroud)
geo*_*org 25
假设数组少于 32 个,您可以使用位图高效地完成此操作。key -> number基本上,如果键位于第 N 个数组中,则构建一个索引,其中数字设置为第 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))Run Code Online (Sandbox Code Playgroud)
(正如评论中所指出的,x & (x-1) === 0检查是否x是二的幂会更惯用。请参阅公式 x & (x - 1) 如何工作?以获取解释。)
这是一种更通用的方法,它不限制数组的数量,也不要求键是字符串:
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))Run Code Online (Sandbox Code Playgroud)
exs*_*ide 10
编辑:误解了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));Run Code Online (Sandbox Code Playgroud)
我认为@gog的答案更复杂,可能更快,但我有点难以理解它(称我为愚蠢,我认为它=D,编辑:必须做一些研究,阅读/学习一些东西关于位集(这里和这里)),所以这里是使用 Map 和数组方法执行此操作的稍微复杂的方法的细分:
0: {"a" => 4}
1: {"b" => 3}
2: {"c" => 3}
3: {"d" => 1}
4: {"f" => 1}
Run Code Online (Sandbox Code Playgroud)
Array.from()创建元组数组将 Map 转换回数组:[
["a", 4],
["b", 3],
["c", 3],
["d", 1],
["f", 1],
]
Run Code Online (Sandbox Code Playgroud)
[<value>, <count>]是只留下恰好出现过一次的值,这样我们就可以得到:[
["d", 1],
["f", 1],
]
Run Code Online (Sandbox Code Playgroud)
["d", "f"]
Run Code Online (Sandbox Code Playgroud)
警告:这段代码在内部执行了大量的循环,所以也称其为暴力循环,由于“性感”的 ES6 array-syntax-sugar,它看起来“更短”。
为了完整性而稍微修改的版本Array.filter(),因为可以通过在完成后迭代 counter-Map 并简单地删除不具有值 1 的 Map 条目来省略该步骤(尽管它似乎更快)。
0: {"a" => 4}
1: {"b" => 3}
2: {"c" => 3}
3: {"d" => 1}
4: {"f" => 1}
Run Code Online (Sandbox Code Playgroud)
更新:正如 @Nick Parsons 指出的那样,代码的前一版本不会输出仅出现在一个数组中的元素,而是多次出现的元素。
如果一个数组多次包含相同的值并且该元素不存在于任何其他数组中,这将产生不正确的输出。例如,如果从 arr2 中删除 b,则只有 arr1 中有 b,而其他没有,因此 b 应该包含在最终结果中。
通过将检查的数组转换为 a Set()(从而将数组值减少为“唯一”值)可以轻松解决此问题。
如果有人(除了我)想知道,这是 gog 的选项和我的选项之间的基准,他的位集方法显然是最快的,所以如果您要比较少于 32 个数组,那么这是迄今为止性能最好的解决方案: https: //jsben.ch /YkKSu
如果有人喜欢 gog 的 bitset 实现的 ES6 化版本(由 @ralphmerridew 建议改进),请看这里:
[
["a", 4],
["b", 3],
["c", 3],
["d", 1],
["f", 1],
]
Run Code Online (Sandbox Code Playgroud)
也用这个更新了基准,有趣的是(或出乎意料地)这个看起来“慢”的 ES6 实现在某种程度上击败了 gog 的 for 循环实现,在 chrome 和 firefox 中进行了多次测试,我自己都不敢相信,想与 for 循环相比,这些语法糖方法会稍微减慢速度,嗯……很高兴知道 =)
我还尝试使用 BigInt() 实现位集方法来消除它只能处理 32 个数组的问题(取决于 BigInt 的引擎,它应该可以处理 100 万到 10 亿个数组),不幸的是,这似乎使其成为所有解决方案中最慢的(基准更新):
[
["d", 1],
["f", 1],
]
Run Code Online (Sandbox Code Playgroud)
也许有人看到了一些可以改进的地方,以使其更快?
这实际上只是集合操作。下面的方法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)Run Code Online (Sandbox Code Playgroud)
借用 @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))
Run Code Online (Sandbox Code Playgroud)
这里的优点是,这应该适用于任意数量的数组,而 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']]))Run Code Online (Sandbox Code Playgroud)
我们通过展平数组数组并将其转换为集合,然后再转换回数组,将数组转换为集合,然后过滤键以仅查找包含该键的集合数量恰好满足的键来找到唯一键的列表。一项。
这里效率有点低,因为我们在查看其中是否有数字时检查所有集合。修改它以仅检查直到找到第二个 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}]
Run Code Online (Sandbox Code Playgroud)
当然,只有当您的项目是共享参考时,这才有帮助。如果您想使用值相等而不是引用相等,那么它会更加复杂。
如果我们想单独传递数组,而不是将它们包装在一个公共数组中,则此变体应该有效:
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)
) ()
Run Code Online (Sandbox Code Playgroud)