包含对象的两个 js 数组的完全外部连接 - 连接的数组包含新的 属性 'action' (add/remove/edit/same)

Full outer join of two js arrays containing objects - joined array contains new property 'action' (add/remove/edit/same)

我有两个数组 originalArraymodifiedArray,它们的对象具有某些属性; sys_id 是独一无二的 属性:

originalArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ABCD' }, 
    { sys_id: 1236, type: 'IJKL' },
    { sys_id: 1237, type: 'WXYZ' }, 
    { sys_id: 1238, type: 'LMNO' }
]

modifiedArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ZZAA' },  
    { sys_id: 1236, type: 'ZZZZ' },
    { sys_id: 1252, type: 'AAAA' }
]

我正在寻找 combine/merge 数组,但包括一个新的 属性,它描述了使用 sys_id 基于原始数组的新数组中更改内容的完整说明属性.

resultingArray = [
    { sys_id: 1234, type: 'XYZZ', action: 'same' }, 
    { sys_id: 1235, type: 'ZZAA', action: 'edit' }, 
    { sys_id: 1236, type: 'ZZZZ', action: 'edit' },
    { sys_id: 1237, type: 'WXYZ', action: 'remove' }, 
    { sys_id: 1238, type: 'LMNO', action: 'remove' },
    { sys_id: 1252, type: 'AAAA', action: 'add' }

还想知道是否有更合适的术语,或者更简洁的方式来解释我在这里试图完成的事情?

我在一个仅限于 ES5 的平台上。

遍历第一个数组,在第二个数组中找到相应的值并基于该生成元素。最后,将剩余的元素添加为新添加的。

var originalArray = [{      sys_id: 1234,      type: 'XYZZ'    },    {      sys_id: 1235,      type: 'ABCD'    },    {      sys_id: 1236,      type: 'IJKL'    },    {      sys_id: 1237,      type: 'WXYZ'    },    {      sys_id: 1238,      type: 'LMNO'    }  ],
  modifiedArray = [{      sys_id: 1234,      type: 'XYZZ'    },    {      sys_id: 1235,      type: 'ZZAA'    },    {      sys_id: 1236,      type: 'ZZZZ'    },    {      sys_id: 1252,      type: 'AAAA'    }  ];
  
  
// keep a swallow copy to not effect the original one
let mA = modifiedArray.slice();

// iterate over to generate new array
let res = originalArray.map(o => {
  // get index of eleemnt
  let moi = mA.findIndex(o1 => o1.sys_id === o.sys_id); 
  // if found 
  if (moi > -1) {
    // remove it from the swallow copied array
    let mo = mA.splice(moi,1)[0];
    // check and generate new array
    return { ...mo, action: mo.type === o.type ? 'same' : 'edited' }
  } else {
    // if not found return as removed
    return { ...o, action: 'removed' }
  }
  // add remaining values as added
}).concat(mA.map(o=>({...o, action: 'added'})))

console.log(res);


ES5 替代方案:

var originalArray = [{      sys_id: 1234,      type: 'XYZZ'    },    {      sys_id: 1235,      type: 'ABCD'    },    {      sys_id: 1236,      type: 'IJKL'    },    {      sys_id: 1237,      type: 'WXYZ'    },    {      sys_id: 1238,      type: 'LMNO'    }  ],
  modifiedArray = [{      sys_id: 1234,      type: 'XYZZ'    },    {      sys_id: 1235,      type: 'ZZAA'    },    {      sys_id: 1236,      type: 'ZZZZ'    },    {      sys_id: 1252,      type: 'AAAA'    }  ];


// function for copying properties to function
function copyProperty(from, to) {
  for (var prop in from) {
    if (from.hasOwnProperty(prop))
      to[prop] = from[prop];
  }
  return to;
}

var mA = modifiedArray.slice();

var res = originalArray.map(function(o) {
  var moi = -1;
  
  // get index by iterating
  for (var i = 0; i < mA.length; i++) {
    if (mA[i].sys_id === o.sys_id) {
      moi = i;
      break;
    }
  }
  if (moi != -1) {
    var mo = mA.splice(moi, 1)[0];
    return copyProperty(mo, { action: mo.type === o.type ? 'same' : 'edited' })
  } else {
    return copyProperty(o, { action: 'removed' })
  }
})

// push remaining values
mA.forEach(function(o) {
  res.push(copyProperty(o, { action: 'added' }))
})

console.log(res);

使用 Array#reduce() 生成使用 sys_id 作为键并具有属性 origmod 的对象,然后使用一个很好的旧 for in 循环来迭代它对象并在 2 种类型存在时进行比较,或者检查哪些类型不存在并将适当的数据推送到结果数组。

所有 ES5 兼容

var grouped = [originalArray, modifiedArray].reduce(function(acc, arr, i) {
  var label = i === 0 ? 'orig' : 'mod';
  for (var j = 0; j < arr.length; j++) {
    var curr = arr[j], id = curr.sys_id;
    acc[id] = acc[id] || {orig: null, mod: null };
    acc[id][label] = curr;
  }
  return acc;
}, {});

var res = [];

function insertObj(o, act) {
  var newObj = { action: act };
  // iterate existing keys to add to new object
  for (var k in o) {
    if (o.hasOwnProperty(k)) {
      newObj[k] = o[k]
    }
  }
  res.push(newObj)
}

for (var key in grouped) {
  var action, obj;
  if (grouped.hasOwnProperty(key)) { 
    obj = grouped[key]
    if (!obj.orig) {
      insertObj(obj.mod, 'add');
    } else if (!obj.mod) {
      insertObj(obj.orig, 'remove')
    } else {
      action = obj.mod.type === obj.orig.type ? 'same' : 'edit';
      insertObj(obj.mod, action)
    }
  }
}

console.log(res)
<script>

var originalArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ABCD' }, 
    { sys_id: 1236, type: 'IJKL' },
    { sys_id: 1237, type: 'WXYZ' }, 
    { sys_id: 1238, type: 'LMNO' }
]

var modifiedArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ZZAA' },  
    { sys_id: 1236, type: 'ZZZZ' },
    { sys_id: 1252, type: 'AAAA' }
]
</script>

只不过是老式的蛮力做事方式。此外,确保原始输入没有突变也很重要。基本上,我遍历所有内容两次,并检查以下条件:

1) 相同 sys_id 和类型(操作:相同)

2) 相同 sys_id,不同类型(操作:编辑)

3) 不同 sys_id,不同类型(操作:删除)

4) 不同 sys_id,不同类型,对象存在于 modifiedArray 但不存在于 originalArray(操作:添加)

(在没有任何 ES6 的情况下写这个让我很痛苦)

var originalArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ABCD' }, 
    { sys_id: 1236, type: 'IJKL' },
    { sys_id: 1237, type: 'WXYZ' }, 
    { sys_id: 1238, type: 'LMNO' }
];

var modifiedArray = [
    { sys_id: 1234, type: 'XYZZ' }, 
    { sys_id: 1235, type: 'ZZAA' },  
    { sys_id: 1236, type: 'ZZZZ' },
    { sys_id: 1252, type: 'AAAA' }
];

var resultingArray = [];

for (var i = 0; i < originalArray.length; i++) {
  for (var j = 0; j < modifiedArray.length; j++) {
    if (originalArray[i].sys_id === modifiedArray[j].sys_id && originalArray[i].type === modifiedArray[j].type) {
      resultingArray.push({
        sys_id: originalArray[i].sys_id,
        type: originalArray[i].type,
        action: 'same'
      });
      break;
    } else if (originalArray[i].sys_id === modifiedArray[j].sys_id && originalArray[i].type !== modifiedArray[j].type) {
      resultingArray.push({
        sys_id: originalArray[i].sys_id,
        type: modifiedArray[j].type,
        action: 'edit'
      });
      break;
    } else if (originalArray[i].sys_id !== modifiedArray[j].sys_id && originalArray[i].type !== modifiedArray[j].type) {
      if (i ===originalArray.length - 1 && j === modifiedArray.length - 1) {
        resultingArray.push({
          sys_id: originalArray[i].sys_id,
          type: modifiedArray[j].type,
          action: 'add'
        });
      } else if (j === modifiedArray.length - 1) {
        resultingArray.push({
          sys_id: originalArray[i].sys_id,
          type: originalArray[i].type,
          action: 'remove'
        });
      }
    }
  }
}

console.log(resultingArray);

Demo

您可以将数组转换为字典。然后迭代原始字典并检查修改后的字典以找到 remove/edit/same,然后迭代修改后的字典与原始字典以找到 add 项。 Concat 两次迭代的结果得到结果:

var originalArray = [{"sys_id":1234,"type":"XYZZ"},{"sys_id":1235,"type":"ABCD"},{"sys_id":1236,"type":"IJKL"},{"sys_id":1237,"type":"WXYZ"},{"sys_id":1238,"type":"LMNO"}];

var modifiedArray = [{"sys_id":1234,"type":"XYZZ"},{"sys_id":1235,"type":"ZZAA"},{"sys_id":1236,"type":"ZZZZ"},{"sys_id":1252,"type":"AAAA"}];

// create a dictionary of objects by the sys_id
function bySysId(arr) {
  return arr.reduce(function(r, o) {
    r[o.sys_id] = o;
    
    return r;
  }, {});
}

// add an action to an object
function addAction(o, action) {
  var c = {
    sys_id: o.sys_id,
    type: o.type,
    action: action
  };
  
  return c;
}

function diffArrays(original, modified) {
  var origById = bySysId(original); // create a dictionary of original by sys_id
  var modById = bySysId(modified); // create a dictionary of modified by sys_id
  
  // iterate original and action
  var modifiedOrSame = original.map(function(o) {
    var mod = modById[o.sys_id];
    
    if(!mod) return addAction(o, 'remove'); // doesn't exist in modById
    else if(mod && mod.type !== o.type) return addAction(mod, 'edit'); // exists in modified but type is different
    
    return addAction(o, 'same'); // haven't changed
  });
  
  var added = modified
    .filter(function(o) { // remove items that are in original
      return !(o.sys_id in origById);
    })
    .map(function(o) { // add the 'add' action to the items
      return addAction(o, 'add');
    });
    
  return modifiedOrSame.concat(added);
}

var result = diffArrays(originalArray, modifiedArray);

console.log(result);

也许,这是最简单的解决方案,它完全支持ES5
首先,将 originalArray 的所有项目推入 resultingArray 并包含 属性
action = 'remove'
此外,将 resultingArray 的元素索引保存到可哈希对象中,其中 key = sys_id

之后遍历 modifiedArray 个元素,如果 sys_id 已经存在于 resultingArray 中,则检查 hash。如果已经存在,比较type。否则,插入一个新元素,其中 action = 'add'

我觉得我的代码比上面的描述更容易理解。开始了:

var originalArray = [{ sys_id: 1234, type: 'XYZZ' },{ sys_id: 1235, type: 'ABCD' },{ sys_id: 1236, type: 'IJKL' },{ sys_id: 1237, type: 'WXYZ' },{ sys_id: 1238, type: 'LMNO' }];

var modifiedArray = [{ sys_id: 1234, type: 'XYZZ' },{ sys_id: 1235, type: 'ZZAA' },{ sys_id: 1236, type: 'ZZZZ' },{ sys_id: 1252, type: 'AAAA' }];

var resultingArray = [],
hash = {},
index = 1;

originalArray.forEach(function(elem) {
    var item = JSON.parse(JSON.stringify(elem));
    item['action'] = 'remove';
    resultingArray.push(item);
    hash[item.sys_id] = index++;
});

modifiedArray.forEach(function(elem) {
    index = hash[elem.sys_id];
    if(index){
        var item = resultingArray[index - 1];
        item.action = (item.type === elem.type) ? 'same' : 'edit';
        return;
    }
    var item = JSON.parse(JSON.stringify(elem));
    item['action'] = 'add';
    resultingArray.push(item);
});

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

注意:在此解决方案中,假设 originalArray 的所有项目都是唯一的,modifiedArray.

也是如此