根据一个键合并两个对象数组

IT技术 javascript
2021-01-29 21:18:40

我有两个数组:

数组 1:

[
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
]

和数组 2:

[
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
]

我需要根据以下条件合并这两个数组id并得到:

[
  { id: "abdc4051", date: "2017-01-24", name: "ab" },
  { id: "abdc4052", date: "2017-01-22", name: "abc" }
]

我怎样才能在不迭代低谷的情况下做到这一点Object.keys

6个回答

你可以这样做 -

let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

let arr3 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));

console.log(arr3);


如果arr1arr2的顺序不同,请使用以下代码

let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...(arr2.find((itmInner) => itmInner.id === arr1[i].id))}
  );
}

console.log(merged);

如果arr1arr2顺序相同,请使用此选项

let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...arr2[i]
  });
}

console.log(merged);

请注意一件事,两个数组必须具有完全相同数量的数据和键。如果一个有 2 把钥匙,另一个有 3 把钥匙,它就行不通了。
2021-03-24 21:18:40
这只是合并数组?它没有像 OP 所要求的那样在 arr1.id == arr2.id 上“加入”。
2021-04-01 21:18:40
标题是“基于一个键合并两个对象数组”。OP 还在“基于 id”的帖子中提到。
2021-04-01 21:18:40
@Dominik 根据 OP 的要求更新了答案。
2021-04-03 21:18:40
这不尊重键/键值。它只是合并数组中的每个项目。问题是:如何按键合并两个数组。对于 arr1,您必须通过键“id”从 arr2 中找到正确的项目。
2021-04-08 21:18:40

您可以在一行中完成此操作

let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

const mergeById = (a1, a2) =>
    a1.map(itm => ({
        ...a2.find((item) => (item.id === itm.id) && item),
        ...itm
    }));

console.log(mergeById(arr1, arr2));

  1. 映射到 array1
  2. 在 array2 中搜索 array1.id
  3. 如果你找到它......将array2的结果传播到array1

最终的数组将只包含与两个数组匹配的 id

伟大的!find 方法中“&& item”的目的是什么?
2021-03-15 21:18:40
&& item不需要。如果没有项目,谓词回调永远不会被调用,那为什么要调用它呢?
2021-03-25 21:18:40
@Fabrice 我的猜测是,在编写答案时,(不正确的)假设是[].find()需要返回找到的项目,而不仅仅是一个布尔值。但是因为它现在在答案中,我们可以弥补它的一些用途:-) 现在它避免匹配 if itemis falsey。所以它有点像 SQL 等三值关系代数中的 JOIN(不会对 NULL 进行 equijoin)。IOW,如果id任何一方丢失或错误,则没有匹配项。
2021-03-27 21:18:40
你不需要&& item这里,find将返回找到的元素。...a2.find(item => item.id === itm.id),
2021-03-31 21:18:40

即使合并后的数组大小不同,此解决方案也适用。此外,即使匹配的键具有不同的名称。

使用 Map 合并两个数组,如下所示:

const arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" },
  { id: "abdc4053", date: "2017-01-22" }
];
const arr2 = [
  { nameId: "abdc4051", name: "ab" },
  { nameId: "abdc4052", name: "abc" }
];

const map = new Map();
arr1.forEach(item => map.set(item.id, item));
arr2.forEach(item => map.set(item.nameId, {...map.get(item.nameId), ...item}));
const mergedArr = Array.from(map.values());

console.log(JSON.stringify(mergedArr));
.as-console-wrapper { max-height: 100% !important; top: 0; }

运行堆栈片段以查看结果:

[
  {
    "id": "abdc4051",
    "date": "2017-01-24",
    "nameId": "abdc4051",
    "name": "ab"
  },
  {
    "id": "abdc4052",
    "date": "2017-01-22",
    "nameId": "abdc4052",
    "name": "abc"
  },
  {
    "id": "abdc4053",
    "date": "2017-01-22"
  }
]
这也解决了我的问题,因为我必须在一个属性上组合并仍然返回未组合的对象。
2021-03-12 21:18:40
这实际上应该是公认的答案
2021-03-28 21:18:40
这是一个比接受的答案更好的答案,因为它允许使用不同的键和不同大小的数组
2021-04-05 21:18:40

您可以使用任意数量的数组并映射到相同索引的新对象。

var array1 = [{ id: "abdc4051", date: "2017-01-24" }, { id: "abdc4052", date: "2017-01-22" }],
    array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }],
    result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

它需要所有数组进行连接并映射a(整个数组)的指定单个元素的结果,稍后c作为 item withb和 item b[i]
2021-03-12 21:18:40
当 id 不同或不按顺序时,此代码不起作用 var array1 = [{ id: "abdc4053", date: "2017-01-24" }, { id: "abdc4054", date: "2017-01-22" }], array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }], result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i]))); console.log(result);
2021-03-29 21:18:40
你能帮我理解这条线result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));吗?这里发生了什么?它是比较两个数组并分配具有公共键的值吗?
2021-04-03 21:18:40

这是使用 reduce 和 Object.assign 的 O(n) 解决方案

const joinById = ( ...lists ) =>
    Object.values(
        lists.reduce(
            ( idx, list ) => {
                list.forEach( ( record ) => {
                    if( idx[ record.id ] )
                        idx[ record.id ] = Object.assign( idx[ record.id ], record)
                    else
                        idx[ record.id ] = record
                } )
                return idx
            },
            {}
        )
    )

要将此函数用于 OP 的情况,请将要加入的数组传递给 joinById(注意列表是一个休息参数)。

let joined = joinById(list1, list2)

每个列表都简化为一个对象,其中键是 id,值是对象。如果在给定的键上已经有一个值,它会调用 object.assign 和当前记录。

这是通用的 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 )

这与 joinById 方法基本相同,只是它保留一个索引对象来标识要加入的记录。记录存储在数组中,索引存储给定键集的记录位置以及在其中找到的列表数。

每次遇到相同的键集时,它都会在树中找到节点,更新其索引处的元素,并递增找到它的次数。

加入后, idx 对象从带有切片的数组中删除,并且在每个集合中找不到的任何元素都将被删除。这使它成为内连接,您可以删除此过滤器并拥有完整的外连接。

最后,每个元素都映射到它的值,并且您有连接的数组。

这是我首选的答案。非常感谢对提出的每个解决方案的详细分析。
2021-03-16 21:18:40
抱歉,这个答案让我无法理解。- 一方面:我应该在哪里插入问题的原始发布者提供的两个示例数组?
2021-03-20 21:18:40
@Henke 我很抱歉没有解释这一点。这两个数组被传递到第一个函数中。您可以复制并粘贴它,将两个数组传入并返回连接的结果。我将通过使用 OP 数据的示例更新答案。
2021-03-24 21:18:40