我正在尝试找出在JS中同时比较/合并/操作两个数组(列表)的最佳/最有效或最具功能性的方法。
我在下面给出的示例是整个概念的简单示例。在我当前的项目中,我处理了一些非常疯狂的列表映射,筛选等,其中包含非常大的对象列表。
如下所述,我比较列表的第一个想法(version1
)将遍历第一个列表(即地图),并在匿名/回调函数中过滤第二个列表以满足所需条件比较(例如匹配ID)。按照下面的version1
,这显然可以正常工作。
我在性能方面有一个问题,因为这种方法在每次地图迭代/调用时,都会过滤整个第二个列表,只是找到一个与过滤器匹配的项目。
此外,过滤器还会通过list2中的所有其他项,这些项应在list1中匹配。含义(因为该句子可能没有意义):
list1.map list2.filter id:1 [id:3,id:2,id:1] ^-match id:2 [id:3,id:2,id:1] ^-match id:3 [id:3,id:2,id:1] ^-match
理想情况下,在地图的第一次迭代(
list1 id:1
)上,当过滤器遇到list2 id:3
(第一项)时,它只会将其与list1 id:3
匹配
考虑到以上概念(与之前遇到的以后的ID匹配,我想到了version2
)。
这会使list2成为字典,然后通过键按任意顺序查找值。
const list1 = [
{id: '1',init:'init1'},
{id: '2',init:'init2'},
{id: '3',init:'init3'}
];
const list2 = [
{id: '2',data:'data2'},
{id: '3',data:'data3'},
{id: '4',data:'data4'}
];
/* ---------
* version 1
*/
const mergedV1 = list1.map(n => (
{...n,...list2.filter(f => f.id===n.id)[0]}
));
/* [
{"id": "1", "init": "init1"},
{"id": "2", "init": "init2", "data": "data2"},
{"id": "3", "init": "init3", "data": "data3"}
] */
/* ---------
* version 2
*/
const dictList2 = list2.reduce((dict,item) => (dict[item.id]=item,dict),{});
// does not handle duplicate ids but I think that's
// outside the context of this question.
const mergedV2 = list1.map(n => ({...n,...dictList2[n.id]}));
/* [
{"id": "1", "init": "init1"},
{"id": "2", "init": "init2", "data": "data2"},
{"id": "3", "init": "init3", "data": "data3"}
] */
JSON.stringify(mergedV1) === JSON.stringify(mergedV2);
// true
// and just for fun
const sqlLeftOuterJoinInJS = list1 => list2 => on => {
const dict = list2.reduce((dict,item) => (
dict[item[on]]=item,dict
),{});
return list1.map(n => ({...n,...dict[n[on]]}
))};
显然,以上示例非常简单(合并两个列表,每个列表的长度为3)。我正在处理更复杂的实例。
我不知道我是否应该使用一些更聪明(理想的功能)技术。
答案 0 :(得分:3)
您可以关闭该组的通缉键,并关闭Map
来收集所有对象。
function merge(key) {
var map = new Map;
return function (r, a) {
a.forEach(o => {
if (!map.has(o[key])) r.push(map.set(o[key], {}).get(o[key]));
Object.assign(map.get(o[key]), o);
});
return r;
};
}
const
list1 = [{ id: '1', init: 'init1' }, { id: '2', init: 'init2' }, { id: '3', init: 'init3' }],
list2 = [{ id: '2', data: 'data2' }, { id: '3', data: 'data3' }, { id: '4', data: 'data4' }],
result = [list1, list2].reduce(merge('id'), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
答案 1 :(得分:1)
使用filter
进行搜索是一个错误的步骤。您对版本2的直觉要好得多。 Map
和Set
提供了更快的查找时间。
这是一种分解的方法。它应该很快,但可能不如Nina快。她是个恶魔> _ << / p>
const merge = (...lists) =>
Array .from
( lists
.reduce (merge1, new Map)
.values ()
)
const merge1 = (cache, list) =>
list .reduce
( (cache, l) =>
cache .has (l.id)
? update (cache, l.id, l)
: insert (cache, l.id, l)
, cache
)
const insert = (cache, key, value) =>
cache .set (key, value)
const update = (cache, key, value) =>
cache .set
( key
, { ...cache .get (key)
, ...value
}
)
const list1 =
[{ id: '1', init: 'init1' }, { id: '2', init: 'init2' }, { id: '3', init: 'init3' }]
const list2 =
[{ id: '2', data: 'data2' }, { id: '3', data: 'data3' }, { id: '4', data: 'data4' }]
console .log (merge (list1, list2))
答案 2 :(得分:0)
我提供此信息是为了完整性,因为我认为Nina和@ user633183提供了最可能更有效的解决方案。
如果您希望坚持最初的 filter 示例,即最大查找N * M,并且您的数组是可变的;您可以考虑在遍历时减少集合。在过去,缩小阵列会对性能产生巨大影响。
当今的一般模式是使用Map(或dict),如其他答案所示,因为它既易于理解又通常有效。
const list1 = [
{id: '1',init:'init1'},
{id: '2',init:'init2'},
{id: '3',init:'init3'}
];
const list2 = [
{id: '2',data:'data2'},
{id: '3',data:'data3'},
{id: '4',data:'data4'}
];
// combine by ID
let merged = list1.reduce((acc, obj)=>{
acc.push(obj);
// find index by ID
let foundIdx = list2.findIndex( el => el.id==obj.id );
// if found, store and remove from search
if ( foundIdx >= 0 ){
obj.data = list2[foundIdx].data;
list2.splice( foundIdx, 1 ); // shrink lookup array
}
return acc;
},[]);
// store remaining (if you want); i.e. {id:4,data:'data4'}
merged = merged.concat(list2)
console.log(merged);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
答案 3 :(得分:0)
我不确定是否应该将此问题标记为duplicate,因为您用不同的措词了。无论如何,这是我的answer逐字复制的问题。您想要的是X.Y.Z
:
equijoin
您可以按以下方式使用它:
const equijoin = (xs, ys, primary, foreign, sel) => {
const ix = xs.reduce((ix, row) => // loop through m items
ix.set(row[primary], row), // populate index for primary table
new Map); // create an index for primary table
return ys.map(row => // loop through n items
sel(ix.get(row[foreign]), // get corresponding row from primary
row)); // select only the columns you need
};
使用const equijoin = (xs, ys, primary, foreign, sel) => {
const ix = xs.reduce((ix, row) => ix.set(row[primary], row), new Map);
return ys.map(row => sel(ix.get(row[foreign]), row));
};
const list1 = [
{ id: "1", init: "init1" },
{ id: "2", init: "init2" },
{ id: "3", init: "init3" }
];
const list2 = [
{ id: "2", data: "data2" },
{ id: "3", data: "data3" },
{ id: "4", data: "data4" }
];
const result = equijoin(list2, list1, "id", "id",
(row2, row1) => ({ ...row1, ...row2 }));
console.log(result);
来计算答案需要O(m + n)
时间。但是,如果您已经有了索引,则只需要equijoin
时间。因此,如果您打算使用相同的表进行多个等值连接,则可能有必要抽象出索引。