比较两个包含对象的数组以计算发生了啥变化?

Posted

技术标签:

【中文标题】比较两个包含对象的数组以计算发生了啥变化?【英文标题】:Compare two arrays containing objects in order to calculate what changed?比较两个包含对象的数组以计算发生了什么变化? 【发布时间】:2017-04-28 13:42:35 【问题描述】:

使用平面 javascript 或通过使用 lodash,我比较以下数组并返回已更改的值的最简单方法是什么(希望 lodash 有一个函数):

之前

[
  id: 0, name: 'Bob', age: 27,
  id: 1, name: 'Frank', age: 32,
  id: 2, name: 'Joe', age: 38
]

之后

[
  id: 0, name: 'Bob', age: 27,
  id: 1, name: 'Frank', age: 33,
  id: 2, name: 'Joe', age: 38
]

所以在前后之间,弗兰克现在已经 33 岁了,那我怎么能简单地返回:

id: 1, name: 'Frank', age: 33

或者更理想的结果:

id: 1, age: 33

编辑:

由于我的问题得到了如此多的答案,我决定在服务器端和客户端对它们进行全部测试。这是我使用json-generator 生成包含 10 000 条记录的 json 文件后得到的结果:

节点 7.1.0

David Domain. (Flat JS filter & some): 3.396
Result:  id: 1, name: 'Frank', age: 33 
Ben Aston (Flat JS nested itteration): 4.359
Result:  age: 33, id: 1 
Gille Q. (Lodash reduce): 21.335
Result:  id: 1, age: 33 
Stasovlas. (Lodash differenceBy): 1.442
Result: []  
Vignesh Murugan. (Lodash findWhere): 0
Result: _.findWhere is not a function

Firefox 50.0.2

David Domain. (Flat JS filter & some): 6.695
Result:  id: 1, name: 'Frank', age: 33 
Ben Aston (Flat JS nested itteration): 10.594
Result:  age: 33, id: 1 
Gille Q. (Lodash reduce): 40.085
Result:  id: 1, age: 33 
Stasovlas. (Lodash differenceBy): 6.499
Result: []

这里要注意的有趣的是,当您处理大量数据时,Lodash differenceBy 似乎不起作用,在我放弃之前我最多只能处理 3 条记录。

@Vignesh 一定曾经使用过 Underscore,但我不打算介绍这一点,因为事情已经发生了变化,我们现在使用 Lodash。

这是我用来测试的代码,用于及时跟踪执行函数所花费的时间,然后循环 1000 次得到执行函数的总时间 1000 次然后除以 1000 得到平均时间执行函数所花费的时间(以毫秒为单位):

var fs = require('fs');
var timely = require('timely');
var _ = require('lodash');

// Ben Aston
var ben_aston = function (a, b) 
  return a.reduce((p,c,i)=>
    var diff = objDiff(c, b[i]);
    diff && p.push(diff);
    return p;
  , [])

function objDiff(a, b) 
  var diff = Object.keys(a).reduce((p,c,i)=>
    if (a[c] === b[c]) 
      return p;
    
    p[c] = b[c];
    return p;
  , );
  if (!Object.keys(diff).length) 
    return;
  
  diff.id = a.id;
  return diff;

var ben_astonT = timely(ben_aston);


// Gille Q.
var gille_q = function (before, after) 
  return _.reduce(before, function(result, value, key) 
    return _.isEqual(value, after[key]) ?
    result : result.concat(id: after[key].id, age: after[key].age);
  , []);

var gille_qT = timely(gille_q);


// David Domain
var david_domain = function (before, after) 
  return after.filter( function( p, idx ) 
    return Object.keys(p).some( function( prop ) 
      return p[prop] !== before[idx][prop];
    )
  )

var david_domainT = timely(david_domain);


// Stasovlas
var stasovlas = function (before, after) 
  return _.differenceBy(after, before, 'age');

var stasovlasT = timely(stasovlas);


// Vignesh Murugan
var vignesh_murugan = function (before, after) 
  before.forEach((current) => 
    var after = _.findWhere(after,id : current.id);
    if(!_.isEqual(after , current)) 
      return _.pick(after,"id","name");
    
  );

var vignesh_muruganT = timely(vignesh_murugan);


// Load the data
var before = JSON.parse(fs.readFileSync('./before.json', 'utf8'));
var after = JSON.parse(fs.readFileSync('./after.json', 'utf8'));

// Open average tracking
var ben_aston_ave = 0,
    gille_q_ave = 0,
    david_domain_ave = 0,
    stasovlas_ave = 0,
    vignesh_murugan_ave = 0;

// Do test
for (i = 0; i < 1000; i++) 
  // Ben Aston
  ben_astonT(before, after);
  ben_aston_ave += ben_astonT.time;

  // Gille Q.
  gille_qT(before, after);
  gille_q_ave += gille_qT.time;

  // David Domain
  david_domainT(before, after);
  david_domain_ave += david_domainT.time;

  // Stasovlas
  stasovlasT(before, after);
  stasovlas_ave += stasovlasT.time;

  // Vignesh Murugan
  // vignesh_muruganT(before, after);
  // vignesh_murugan_ave += vignesh_muruganT.time;


// Calc averages
ben_aston_ave = ben_aston_ave / 1000;
gille_q_ave = gille_q_ave / 1000;
david_domain_ave = david_domain_ave / 1000;
stasovlas_ave = stasovlas_ave / 1000;
vignesh_murugan_ave = vignesh_murugan_ave / 1000;


console.log('David Domain. (Flat JS filter & some): '+david_domain_ave);
console.log('Result:  id: 1, name: \'Frank\', age: 33 ');
console.log('Ben Aston (Flat JS nested itteration): '+ben_aston_ave);
console.log('Result:  age: 33, id: 1 ');
console.log('Gille Q. (Lodash reduce): '+gille_q_ave);
console.log('Result:  id: 1, age: 33 ');
console.log('Stasovlas. (Lodash differenceBy): '+stasovlas_ave);
console.log('Result: []');
console.log('Vignesh Murugan. (Lodash findWhere): '+vignesh_murugan_ave);
console.log('Result: _.findWhere is not a function');

【问题讨论】:

您可以发布您尝试过的内容吗? @SankarRaj 我尝试获取结果并与之前的数组进行比较,但这不是一个好主意,因为数据量很大。不想通过为此提供代码来向任何一个方向推送任何可能的答案,因为我想看看有哪些选项可用。也许只是因为缺少正确的术语,我不确定? ***.com/questions/13147278/… 或其他类似问题之一 物体会比这更深吗?如果不是,您可以在将它们字符串化后尝试进行字符串比较 @Goliadkin 不,他们总是这样,但这是一个聪明的主意。我认为它也会很快。我会考虑一下并尝试一下,谢谢! 【参考方案1】:

我试过用下划线,效果很好

  var x = [
  id: 0, name: 'Bob', age: 27,
  id: 1, name: 'Frank', age: 32,
  id: 2, name: 'Joe', age: 38
]

var y = [
  id: 0, name: 'Bob', age: 27,
  id: 1, name: 'Frank', age: 33,
  id: 2, name: 'Joe', age: 38
]

  x.forEach((current) => 
    var after = _.findWhere(y,id : current.id)
    if(!_.isEqual(after , current))
      console.log(_.pick(after,"id","name"))
    
  )

这是 Lodash 解决方案

var difference = [];

x.forEach((current) => 
  var after = _.find(y,id : current.id)
  if(!_.isEqual(after , current))
    difference.push(_.pick(after,"id","name"))
  
)

console.log(difference)

【讨论】:

您好,请查看我的问题的编辑,发布了一些您可能会感兴趣的基准测试结果。您的解决方案没有放在我的基准测试中,因为它具有 Underscore.js 并且有问题的功能在 Lodash 中不再有效。【参考方案2】:

假设数组索引保持不变:

function diff(a, b) 
    return a.reduce((p,c,i)=>
        var diff = objDiff(c, b[i]);
        diff && p.push(diff);
        return p;
    , [])

function objDiff(a, b) 
    var diff = Object.keys(a).reduce((p,c,i)=>
        if (a[c] === b[c]) 
            return p;
        
        p[c] = b[c];
        return p;
    , );
    if (!Object.keys(diff).length) 
        return;
    
    diff.id = a.id;
    return diff;


const before = [
    id: 0, name: 'Bob', age: 27 , 
    id: 1, name: 'Frank', age: 32 , 
    id: 2, name: 'Joe', age: 38 ]
const after = [
    id: 0, name: 'Bob', age: 27 , 
    id: 1, name: 'Frank', age: 33 , 
    id: 2, name: 'Joe', age: 38 ];

console.log(diff(before, after));

【讨论】:

谢谢 Ben,非常感谢您在这里的时间和精力,我也想知道这种方法有什么名字吗?我想这就是 lodash 在下面所做的。对所有这些示例进行基准测试,看看哪个是最快的,只是为了好玩:) 这实际上是一个嵌套迭代。不知道有没有更好的形容词。 请注意,此解决方案假定数组中的位置很重要。 您好,请查看我的问题的编辑,发布了一些您可能会感兴趣的基准测试结果。您的解决方案排名第二。【参考方案3】:

您也可以使用 lodash reduce 方法进行比较,这是我为您的示例制作的代码,它在此 jsfiddle 中返回您想要的内容:

https://jsfiddle.net/7rf9bphL/1/

var a = [
    id: 0, name: 'Bob', age: 27,
    id: 1, name: 'Frank', age: 32,
    id: 2, name: 'Joe', age: 38];

var b = [
    id: 0, name: 'Bob', age: 27,
    id: 1, name: 'Frank', age: 33,
    id: 2, name: 'Joe', age: 38];

var result = _.reduce(a, function(result, value, key) 
    return _.isEqual(value, b[key]) ?
        result : result.concat(id: b[key].id, age: b[key].age);
, []);
console.log("result", result);

【讨论】:

呵呵,我实际上也尝试过使用 reduce 来做到这一点,但没有成功,现在可以看到原因了。谢谢,感谢您的时间和精力,将对示例进行基准测试,看看哪个最快! @CraigvanTonder 总是乐于提供帮助,不客气! 您好,请查看我的问题的编辑,发布了一些您可能会感兴趣的基准测试结果。您的解决方案排名第三。 是的,这很有趣,reduce 对于大量数据并不是很强大,感谢您的测试:)【参考方案4】:

您可以将Array.filterArray.some 一起使用,这将为您提供一个包含已更改项目的新数组。

可能是这样的:

var before = [
  id: 0, name: 'Bob', age: 27,
  id: 1, name: 'Frank', age: 32,
  id: 2, name: 'Joe', age: 38
]

var after = [
  id: 0, name: 'Bobb', age: 27,
  id: 1, name: 'Frank', age: 33,
  id: 2, name: 'Joe', age: 38
]

var changed = after.filter( function( p, idx ) 
  return Object.keys(p).some( function( prop ) 
    return p[prop] !== before[idx][prop];
  )
)

console.log(changed)
.as-console-wrapper 
  max-height: 100% !important;

【讨论】:

谢谢,这几乎是我自己尝试做的,但无法做到。我感谢答案,因为它帮助我学习!我将对提供的示例进行基准测试,看看哪个是最快的:) 当然,没问题。请注意,这只是普通的 JavaScript,不需要 lodash。 我很感激,是的,这使它成为我心目中更好的解决方案。只有少数地方我使用了 lodash,这是因为在这一点上得出结论的方法有点超出我的范围。看到做其他复杂事情的简单方法总是有帮助的,这确实是我可以理解和使用的一种,lodash的-1用例,也许有一天我根本不需要它:) 您好,请查看我的问题的编辑,发布了一些您可能会感兴趣的基准测试结果。您的解决方案排名第一。【参考方案5】:

使用_.differenceBy

var res = _.differenceBy(after, before, 'age');

【讨论】:

非常感谢,这看起来像是赢家,但我将对提供的示例进行基准测试,看看哪个是最快的:) 您好,请查看我的问题的编辑,发布了一些您可能会感兴趣的基准测试结果。您的解决方案没有放在我的基准测试中,因为它不适用于更大的数据集。奇怪但真实的事情,除非你能证明否则我相信大卫对这个问题有最好的答案? @CraigvanTonder 干得好!嗯,这真的很奇怪,我认为最好问问大卫。并将他的答案也分享给我,请

以上是关于比较两个包含对象的数组以计算发生了啥变化?的主要内容,如果未能解决你的问题,请参考以下文章

在 2 个空数组的这个松散相等比较中发生了啥

GCM 应用服务器文档发生了啥变化? [关闭]

周末 Google Directory API 发生了啥变化/中断?

Vue计算属性和监听属性

当实体发生变化时,OpenJPA 对 db 模式做了啥?

在 numpy 添加过程中隐含发生了啥?