2つのオブジェクト配列があります。
_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'},
]
_
これらの2つの配列a
とb
に対して内部結合を実行し、次のような3番目の配列を作成します(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)
でそれを行うにはどうすればよいですか?私の友人は、レデューサーと_Object.assign
_を使用できると言っていました。
私はこれを理解することができません。助けてください。
それを解決する方法の1つ。
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(newArr);
ここでreduce
がどのように役立つかはわかりませんが、Map
を使用してO(n)
で同じタスクを実行できます。
var m = new Map();
// Insert all entries keyed by ID into map, filling in placeholder position
// since a lacks position entirely
a.forEach(function(x) { x.position = null; m.set(x.id, x); });
// For b values, 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());
Map
のアクセスと更新はO(1)
であるため(平均的な場合、ハッシュの衝突と再ハッシュのおかげで長くなる可能性があります)、これによりO(n+m)
(ここでn
とm
はそれぞれa
とb
の長さです。あなたが与えた素朴な解決策は、n
と同じ意味を使用するO(n*m)
になります。およびm
)。
時間の複雑さを軽減するために、より多くのメモリを使用することは避けられません。
_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が述べたように、「検索をネイティブコードに移動しても、問題の複雑さを軽減することはできません。」 Set.prototype.has()
とMap.prototype.get()
の内部プロシージャについて_ECMAScript® 2016 Language Specification
_に相談しましたが、残念ながら、両方とも持っているすべての要素を反復処理しているように見えました。
_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の使用を嫌う多くの理由の1つです。
したがって、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オブジェクトがスライスとともに配列から削除され、各セットで見つからなかった要素がすべて削除されます。これにより、内部結合になります。このフィルターを削除して、完全な外部結合を作成できます。
最後に、各要素がその値にマップされ、配列がマージされます。