[英]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.