簡體   English   中英

如何在 JavaScript 中使用兩個對象數組執行內部聯接?

[英]How can I perform an inner join with two object arrays in JavaScript?

我有兩個對象數組:

var a = [
  {id: 4, name: 'Greg'},
  {id: 1, name: 'David'},
  {id: 2, name: 'John'},
  {id: 3, name: 'Matt'},
]

var b = [
  {id: 5, name: 'Mathew', position: '1'},
  {id: 6, name: 'Gracia', position: '2'},
  {id: 2, name: 'John', position: '2'},
  {id: 3, name: 'Matt', position: '2'},
]

我想對這兩個數組ab進行內部連接,並創建這樣的第三個數組(如果 position 屬性不存在,則它變為 null):

var result = [{
  {id: 4, name: 'Greg', position: null},
  {id: 1, name: 'David', position: null},
  {id: 5, name: 'Mathew', position: '1'},
  {id: 6, name: 'Gracia', position: '2'},
  {id: 2, name: 'John', position: '2'},
  {id: 3, name: 'Matt', position: '2'},
}]

我的做法:

function innerJoinAB(a,b) {
    a.forEach(function(obj, index) {
        // Search through objects in first loop
        b.forEach(function(obj2,i2){
        // Find objects in 2nd loop
        // if obj1 is present in obj2 then push to result.
        });
    });
}

但是時間復雜度是O(N^2) 我怎樣才能在O(N)做到這一點? 我的朋友告訴我,我們可以使用 reducers 和Object.assign

我無法弄清楚這一點。 請幫忙。

我不知道reduce在這里有什么幫助,但是您可以使用MapO(n)完成相同的任務:

 const a = [ {id: 4, name: 'Greg'}, {id: 1, name: 'David'}, {id: 2, name: 'John'}, {id: 3, name: 'Matt'}]; const b = [ {id: 5, name: 'Mathew', position: '1'}, {id: 6, name: 'Gracia', position: '2'}, {id: 2, name: 'John', position: '2'}, {id: 3, name: 'Matt', position: '2'}]; var m = new Map(); // Insert all entries keyed by ID into the Map, filling in placeholder // 'position' since the Array 'a' lacks 'position' entirely: a.forEach(function(x) { x.position = null; m.set(x.id, x); }); // For values in 'b', insert them if missing, otherwise, update existing values: b.forEach(function(x) { var existing = m.get(x.id); if (existing === undefined) m.set(x.id, x); else Object.assign(existing, x); }); // Extract resulting combined objects from the Map as an Array var result = Array.from(m.values()); console.log(JSON.stringify(result));
 .as-console-wrapper { max-height: 100% !important; top: 0; }

因為Map訪問和更新是O(1) (平均而言 - 因為散列沖突和重新散列,它可以更長),這使得O(n+m) (其中nm分別是ab的長度;您給出的天真的解決方案將是O(n*m)nm使用相同的含義)。

如何解決它的方法之一。

 const a = [ {id: 4, name: 'Greg'}, {id: 1, name: 'David'}, {id: 2, name: 'John'}, {id: 3, name: 'Matt'}, ]; const b = [ {id: 5, name: 'Mathew', position: '1'}, {id: 6, name: 'Gracia', position: '2'}, {id: 2, name: 'John', position: '2'}, {id: 3, name: 'Matt', position: '2'}, ]; const r = a.filter(({ id: idv }) => b.every(({ id: idc }) => idv !== idc)); const newArr = b.concat(r).map((v) => v.position ? v : { ...v, position: null }); console.log(JSON.stringify(newArr));
 .as-console-wrapper { max-height: 100% !important; top: 0; }

為了降低時間復雜度,使用更多的內存是不可避免的。

 var a = [ {id: 4, name: 'Greg'}, {id: 1, name: 'David'}, {id: 2, name: 'John'}, {id: 3, name: 'Matt'}, ] var b = [ {id: 5, name: 'Mathew', position: '1'}, {id: 6, name: 'Gracia', position: '2'}, {id: 2, name: 'John', position: '2'}, {id: 3, name: 'Matt', position: '2'}, ] var s = new Set(); var result = []; b.forEach(function(e) { result.push(Object.assign({}, e)); s.add(e.id); }); a.forEach(function(e) { if (!s.has(e.id)) { var temp = Object.assign({}, e); temp.position = null; result.push(temp); } }); console.log(result);

更新

正如@Blindman67 所提到的:“您不會通過將搜索移到本機代碼中來降低問題的復雜性。” 我已經查閱了ECMAScript® 2016 Language Specification關於Set.prototype.has()Map.prototype.get()的內部過程,不幸的是,它們似乎都遍歷了它們擁有的所有元素。

Set.prototype.has ( value )#

The following steps are taken:

    Let S be the this value.
    If Type(S) is not Object, throw a TypeError exception.
    If S does not have a [[SetData]] internal slot, throw a TypeError exception.
    Let entries be the List that is the value of S's [[SetData]] internal slot.
    Repeat for each e that is an element of entries,
        If e is not empty and SameValueZero(e, value) is true, return true.
    Return false. 

http://www.ecma-international.org/ecma-262/7.0/#sec-set.prototype.has

Map.prototype.get ( key )#

The following steps are taken:

    Let M be the this value.
    If Type(M) is not Object, throw a TypeError exception.
    If M does not have a [[MapData]] internal slot, throw a TypeError exception.
    Let entries be the List that is the value of M's [[MapData]] internal slot.
    Repeat for each Record {[[Key]], [[Value]]} p that is an element of entries,
        If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]].
    Return undefined. 

http://www.ecma-international.org/ecma-262/7.0/#sec-map.prototype.get

也許,我們可以使用可以通過名稱直接訪問其屬性的Object ,例如哈希表或關聯數組,例如:

 var a = [ {id: 4, name: 'Greg'}, {id: 1, name: 'David'}, {id: 2, name: 'John'}, {id: 3, name: 'Matt'}, ] var b = [ {id: 5, name: 'Mathew', position: '1'}, {id: 6, name: 'Gracia', position: '2'}, {id: 2, name: 'John', position: '2'}, {id: 3, name: 'Matt', position: '2'}, ] var s = {}; var result = []; b.forEach(function(e) { result.push(Object.assign({}, e)); s[e.id] = true; }); a.forEach(function(e) { if (!s[e.id]) { var temp = Object.assign({}, e); temp.position = null; result.push(temp); } }); console.log(result);

您不會通過將搜索移動到本機代碼中來降低問題的復雜性。 搜索仍然必須進行。

此外,需要將未定義的屬性設為 null 也是我不喜歡使用 null 的眾多原因之一。

所以沒有 null 解決方案看起來像

var a = [
  {id: 4, name: 'Greg',position: '7'},
  {id: 1, name: 'David'},
  {id: 2, name: 'John'},
  {id: 3, name: 'Matt'},
]

var b = [
  {id: 5, name: 'Mathew', position: '1'},
  {id: 6, name: 'Gracia', position: '2'},
  {id: 2, name: 'John', position: '2'},
  {id: 3, name: 'Matt', position: '2'},
]


function join (indexName, ...arrays) {
    const map = new Map();
    arrays.forEach((array) => {
        array.forEach((item) => {
            map.set(
                item[indexName],
                Object.assign(item, map.get(item[indexName]))
            );
        })
    })
    return [...map.values()];
}

並被稱為

const joinedArray = join("id", a, b);

加入默認值有點復雜,但應該證明很方便,因為它可以加入任意數量的數組並自動將缺少的屬性設置為提供的默認值。

連接后對默認值進行測試以節省一點時間。

function join (indexName, defaults, ...arrays) {
    const map = new Map();
    arrays.forEach((array) => {
        array.forEach((item) => {
            map.set(
                item[indexName], 
                Object.assign( 
                    item, 
                    map.get(item[indexName])
                )
            );
        })
    })
    return [...map.values()].map(item => Object.assign({}, defaults, item));

}

使用

const joinedArray = join("id", {position : null}, a, b);

你可以加...

    arrays.shift().forEach((item) => {  // first array is a special case.
        map.set(item[indexName], item);
    });

...在函數的開頭節省了一點時間,但我覺得沒有額外的代碼更優雅。

如果您放棄null標准(社區中的許多人都說使用 null 不好),那么有一個非常簡單的解決方案

let a = [1, 2, 3];
let b = [2, 3, 4];

a.filter(x => b.includes(x)) 

// [2, 3]

這是對連接的更通用版本的嘗試,它接受 N 個對象並根據主id鍵合並它們。

如果性能至關重要,最好使用特定版本,例如 ShadowRanger 提供的版本,它不需要動態構建所有屬性鍵的列表。

此實現假定任何缺失的屬性都應設置為 null,並且每個輸入數組中的每個對象都具有相同的屬性(盡管數組之間的屬性可能不同)

 var a = [ {id: 4, name: 'Greg'}, {id: 1, name: 'David'}, {id: 2, name: 'John'}, {id: 3, name: 'Matt'}, ]; var b = [ {id: 5, name: 'Mathew', position: '1'}, {id: 600, name: 'Gracia', position: '2'}, {id: 2, name: 'John', position: '2'}, {id: 3, name: 'Matt', position: '2'}, ]; console.log(genericJoin(a, b)); function genericJoin(...input) { //Get all possible keys let template = new Set(); input.forEach(arr => { if (arr.length) { Object.keys(arr[0]).forEach(key => { template.add(key); }); } }); // Merge arrays input = input.reduce((a, b) => a.concat(b)); // Merge items with duplicate ids let result = new Map(); input.forEach(item => { result.set(item.id, Object.assign((result.get(item.id) || {}), item)); }); // Convert the map back to an array of objects // and set any missing properties to null return Array.from(result.values(), item => { template.forEach(key => { item[key] = item[key] || null; }); return item; }); }

這是一個通用的 O(n*m) 解決方案,其中 n 是記錄數,m 是鍵數。 這僅適用於有效的對象鍵。 您可以將任何值轉換為 base64,並在需要時使用它。

const join = ( keys, ...lists ) =>
    lists.reduce(
        ( res, list ) => {
            list.forEach( ( record ) => {
                let hasNode = keys.reduce(
                    ( idx, key ) => idx && idx[ record[ key ] ],
                    res[ 0 ].tree
                )
                if( hasNode ) {
                    const i = hasNode.i
                    Object.assign( res[ i ].value, record )
                    res[ i ].found++
                } else {
                    let node = keys.reduce( ( idx, key ) => {
                        if( idx[ record[ key ] ] )
                            return idx[ record[ key ] ]
                        else
                            idx[ record[ key ] ] = {}
                        return idx[ record[ key ] ]
                    }, res[ 0 ].tree )
                    node.i = res[ 0 ].i++
                    res[ node.i ] = {
                        found: 1,
                        value: record
                    }
                }
            } )
            return res
        },
        [ { i: 1, tree: {} } ]
         )
         .slice( 1 )
         .filter( node => node.found === lists.length )
         .map( n => n.value )

join( [ 'id', 'name' ], a, b )

這與 Blindman67 的答案基本相同,只是它添加了一個索引對象來標識要加入的記錄。 記錄存儲在數組中,索引存儲給定鍵集的記錄位置以及在其中找到的列表數。

每次遇到相同的鍵集時,都會在樹中找到節點,更新其索引處的元素,並增加找到它的次數。

最后,idx 對象從帶有切片的數組中刪除,在每個集合中找不到的任何元素都將被刪除。 這使其成為內連接,您可以刪除此過濾器並擁有完整的外連接。

最后,每個元素都映射到它的值,並且您有合並的數組。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM