根据匹配的 ID 合并两个数组中的项目

Posted

技术标签:

【中文标题】根据匹配的 ID 合并两个数组中的项目【英文标题】:Merge items from two arrays based on matching ID 【发布时间】:2019-09-06 13:55:51 【问题描述】:

我有一个这样的数据对象:


  "data1": [
    [
      "ID",
      "name",
      "Birthday"
    ],
    [
      "10",
      "thomas",
      "1992-03-17"
    ],
    [
      "11",
      "Emily",
      "2000-03-03"
    ]
  ],
  "data2": [
    [
      "Balance",
      "ID"
    ],
    [
      "$4500",
      "10"
    ],
    [
      "$1500",
      "13"
    ]
  ]

它包含两个数组data1data2。 每个数组的第一行是列的名称,其余行包含数据(将其视为表格)。

我想比较两个数组中的ID 字段,如果IDs 匹配,那么最终输出将包含Balance 列,余额对应于ID,如果IDs不匹配则Balance 将是$0

预期输出:


  "output": [
    [
      "ID",
      "name",
      "Birthday",
      "Balance"
    ],
    [
      "10",
      "thomas",
      "1992-03-17",
      "$4500" //ID 10 matched so the balance added here
    ],
    [
      "11",
      "Emily",
      "2000-03-03",
      "0" //0 bcoz the ID 11 is not there in data2 array
    ]
  ]


我觉得这很难完成。把它想象成 mysql 中的 LEFT-JOIN。 我提到了这个solution,但它在我的情况下不起作用,因为我的回复中没有密钥。

编辑:我还需要加入其他领域。

【问题讨论】:

【参考方案1】:

您可以使用Array.prototype.map()、find、filter、slice、reduce、concat、includes 和 Object.assign()。

这个解决方案:

处理项目的任意顺序。订单从标题中读取。 仅当data2 中存在Balance 字段时才附加一个字段。 加入所有其他字段(由 OP 要求,参见下面的 cmets)。 将默认值作为输入,如果data1data2 中不存在数据,则使用它们。

function merge( data1, data2 , defaults) 
  // get the final headers, add/move 'Balance' to the end
  const headers = [...data1[0].filter(x => x !== 'Balance')]
    .concat(data2[0].includes('Balance') ? ['Balance'] : []);
  
  // map the data from data1 to an array of objects, each key is the header name, also merge the default values.
  const d1 = data1.slice(1)
    .map(x => x.reduce((acc, y, i) => ( ...defaults, ...acc, [data1[0][i]]: y ), ));
  // map the data from data2 to an array of objects, each key is the header name
  const d2 = data2.slice(1)
    .map(x => x.reduce((acc, y, i) => ( ...acc, [data2[0][i]]: y ), ));
  
  // combine d1 and d2
  const output = d1.map((x, i) =>  // iterate over d1
    // merge values from d2 into this value
    const d = Object.assign(x, d2.find(y => y['ID'] === x['ID']));
    // return an array ordered according to the header
    return headers.map(h => d[h]);
  );
  return  output: [headers, ...output] ;


const test0 = 
  data1: [[ "ID","name","Birthday","other"],["10","thomas","1992-03-17","empty"],["11","Emily","2000-03-03","empty"]],
  data2: [["other", "ID", "Balance", "city"],["hello", "10", "$4500", "New York"],["world", "10","$8","Brazil"]]
;

const test1 = 
  data1: [["ID","name","Birthday"],["10","thomas","1992-03-17"],["11","Emily","2000-03-03"]],
  data2: [["other","ID"],["x","10"],["y","11"]]
;

console.log(merge(test0,  Balance: '$0' ));
console.log(merge(test1,  Balance: '$0' ));

【讨论】:

这个解决方案是不可扩展的,如果你添加像“Balance”这样的属性,它不会接受它们。 @karkael,我更新了代码以处理项目的任意排序。 将此标记为已接受的答案,因为它非常适合我的给定场景。它很整洁,我可以重复使用该功能 请在代码中添加 cmets 行 return i === 0 好吗? [...x, 'Balance'] : [...x, (data.data2.find((y) => y[idIdx2] === x[idIdx1]) || [])[balanceIdx2] | | '$0' 解释了代码的确切作用,因为我在理解这部分时遇到了一点困难: return i === 0 ? [...x, '平衡'] 非常感谢@jo_va。你真的很有帮助。【参考方案2】:
const KEY_ID = "ID";

var data = 
  "data1": [
    [ "ID", "name", "Birthday" ],
    [ "10", "thomas", "1992-03-17" ],
    [ "11", "Emily", "2000-03-03" ]
  ],
  "data2": [
    [ "Balance", "ID" ],
    [ "$4500", "10" ],
    [ "$1500", "13" ]
  ]


var merged = Object.keys(data).map(function (key) 
  var tmp = data[key].slice();
  var heads = tmp.shift();
  return tmp.map(function (item) 
    var row = ;
    heads.forEach(function (head, i) 
      row[head] = item[i];
    );
    return row;
  );
).flat().reduce(function (acc, row) 
  var found = acc.find(function (item) 
    return row[KEY_ID] === item[KEY_ID];
  )
  if (!found) 
    found = row;
    acc.push(found);
   else 
    Object.keys(row).forEach(function (head) 
      found[head] = row[head];
    );
  
  return acc;
, []);

console.log(merged);

此解决方案是可扩展的:如果您添加属性,它将扩展新格式。

【讨论】:

【参考方案3】:
let a =  "data1": [ ... ],"data2": [ ...] 
let r = a.data1.reduce((r,u,i)=>
  if(i !== 0)
  
    let entry = a.data2.filter((a)=> a[1]===u[0])
    r.push([...u,entry.length ? entry[0][0] : 0])
  
   return r
,[[
      "ID",
      "name",
      "Birthday",
      "Balance"
]])


【讨论】:

【参考方案4】:

您可以将所有表操作抽象为类:

 function Table(array) 
   const [head, ...values] = array;

  const Entry =(entry) => (
   get(key)  return entry[ head.indexOf(key) ]; ,
   set(key, value)  entry[ head.indexOf(key) ] = value; 
  );

  return 
    index(name)        
      const result = ;
      for(const value of values)
        result[ value[ head.indexOf(name) ] ] = Entry(value);

      return result;
    ,
    *[Symbol.iterator]() 
      for(const row of values)
         yield Entry(row);
     ,

      addRow(key)  head.push(key); 
   ;
 

可用作:

 const users = Table(obj.data1);
 const balances = Table(obj.data2);

 const balanceByID = balance.index("ID");

 users.addRow("Balance");

 for(const user of users)
   user.set("Balance", balanceByID[ user.get("ID") ].get("Balance"));

【讨论】:

以上是关于根据匹配的 ID 合并两个数组中的项目的主要内容,如果未能解决你的问题,请参考以下文章

无法将 Django 两个对象数组中的项目 ID 与 if 条件匹配

如何将不同数组中的两个匹配对象合并为一个对象?

SQL:选择 * where 属性匹配数组中的所有项目

如果匹配 ID,则合并数组并为其添加标志

根据列中的匹配数据合并两个 Excel 表

sql 多对多查询结果仅当仅匹配且完全匹配数组中的所有项目时