比较两个嵌套数组和对象以查找差异

时间:2019-11-07 19:31:40

标签: javascript arrays algorithm compare

我有两个带有多级嵌套的JSON数据。我想通过以下条件进行比较: 1)如果第一个对象中的名称等于第二个对象中的名称,则比较它们的prop数组;否则,如果两个对象中没有相等的名称,则返回空数组; 2)将对象比较成两个prop数组并找出差异; 3)返回与第一个数组和第二个数组不同的新对象。

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

结果应如下所示:

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

我在这里也很痛苦地附加了我一文不值的代码,什么都不做。

const compare = (p1, p2) => {
  return p1.reduce((acc, curr) => {
    p2.reduce((acc2, curr2) => {
      if (curr.name === curr2.name) {
        const keys1 = R.fromPairs(Object.keys(curr.prop[0]).map(x => ([x, curr.prop[0][x]])));
        const keys2 = R.fromPairs(Object.keys(curr2.prop[0]).map(x => ([x, curr2.prop[0][x]])));
      }
      return acc;
    }, [])

    return acc;
  }, [])
}

我将非常感谢您的帮助和建议。

2 个答案:

答案 0 :(得分:3)

所有困难都在于指定比较功能的预期行为:

对于两个对象(我称为值)ab{A:1,B:2}{A:1,B:3,C:4} cmp(a,b)的输出应为:

  foreach key of a:
    if a[key] != b[key] (or b does not have k prop)
      diff[key] = a[key]
    else (value is equal, no diff)
  foreach key of b not in a
    diff[key] = Missing

因此(例如){B:2, C:'Missing'}

在比较值时,如果diff为空,则可以跳过当前属性;在比较属性时,如果diff为空,则跳过记录(就像名称不同)

function cmp(x,y){
  let a = x.prop[0];
  let b = y.prop[0];
  return Object.keys(a).reduce((o,k)=>{
    //compare the right value (such as { A: 1, B: 2 }). assumes primitive types
    let u = a[k];
    let v = b[k];
    
    let diff = Object.keys(u).reduce((o,k)=>{
      return u[k]==v[k]?o:(o[k] = u[k],o)
    },{})
    
    Object.keys(v).reduce((o,k)=>{
      return u.hasOwnProperty(k)?o:(o[k]='Missing',o);
    }, diff);
    
    if(Object.keys(diff).length){
      o[k] = diff;
    }
    
    return o;
  },{});
}
function diff(p1,p2){
  return p1.flatMap((o,i)=>{
    if(p2[i].name != p1[i].name){
      return []
    }

    let a = p1[i];
    let b = p2[i];
    let res = cmp(a,b);
    
    if(!Object.keys(res).length){
      return [];
    }
    
    return {name: a.name, propOne:res, propTwo:cmp(b,a)}
  })
};
const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}];

console.log('result', JSON.stringify(diff(p1,p2),null,2))

答案 1 :(得分:0)

有点晚了,我现在没有时间进行大量测试,因此这可能会出现一些意外情况的错误(我希望不是)。发表评论的时间太长了,放弃评论会很浪费。

下面的代码包含两种方式:一种是建设性的,以空对象开始,构建,另一种是围绕性的,以完整对象开始,然后删除。

请注意,在您的数据结构中,有多个“一个元素数组”。如果这些元素可以包含一个以上的元素(对我而言它们并没有多大意义,它已经是数组中数组中的对象,为其他道具提供了足够的空间),则需要额外增加一两个map步骤,不过没什么大问题。

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    C: { C: 78, D: 4, T: 7, }
  }],
}, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4, Y: 13 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{
    A: { A: 1, B: 8 },
    B: { A: 1, B: 2 },
    C: { C: 3, T: 7, O: 9 }
  }],
}, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2, U: 150 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

const diffDestructive = (a, b) => {
  /**
   * Copy the objects, remove all identical properties recursively,
   * then add "Missing" properties for all properties from either side
   * that doesn't exist on the other.
   */

  const remove = (x, y) => {
    for (let key of Object.keys(x)) {
      // hasOwnProperty is only for the degenerate case { prop: undefined }
      if (x[key] === y[key] && y.hasOwnProperty(key)) {
        delete x[key];
        delete y[key];
      }     // typeof null === "object", therefore an additional check is needed
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object") {
        remove(x[key], y[key]);
        if ([x, y].every(e => Object.keys(e[key]).length === 0)) {
          delete x[key];
          delete y[key];
        }
      }
    }
  };

  const addMissingNotes = (x, y) => {
    for (let key of Object.keys(x)) {
      if (!(y.hasOwnProperty(key))) y[key] = "Missing";
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object")
        addMissingNotes(x[key], y[key]);
    }
  };

  // quick and dirty object deep-copy
  let [modA, modB] = [a, b].map(e => JSON.parse(JSON.stringify(e)));

  remove(modA, modB);
  addMissingNotes(modA, modB);
  addMissingNotes(modB, modA);

  return [modA, modB];
};

const diffConstructive = (a, b) => {
  /**
   * Add differing properties to the result step by step.
   * Nested objects are handled recursively.
   */

  let diffA = {}, diffB = {};

  for (let key of Object.keys(a)) {
    //properties that a and b share
    if (b.hasOwnProperty(key)) {
      if (a[key] && typeof a[key] === "object" && b[key] && typeof b[key] === "object") {
        let subDiffs = diffConstructive(a[key], b[key]);
        // The way the construction works, Object.keys(subDiffs[0]).length !== 0 would be enough.
        if (subDiffs.some(e => Object.keys(e).length !== 0)) {
          [diffA[key], diffB[key]] = subDiffs;
        }
      } else if (a[key] !== b[key]) {
        diffA[key] = a[key];
        diffB[key] = b[key];
      }
    } // properties that a has but b doesn't
    else {
      diffA[key] = a[key];
      diffB[key] = "Missing";
    }
  }

  // properties that b has but a doesn't
  for (let key of Object.keys(b)) {
    if (!a.hasOwnProperty(key)) {
      diffB[key] = b[key];
      diffA[key] = "Missing";
    }
  }

  return [diffA, diffB];
};

const compare = (a, b, method) => a
  .map((e, i) => [e, b[i]])
  //same name only
  .filter(([a, b]) => a.name === b.name)
  // formatting
  .map(([a, b]) => {
    const [diffA, diffB] = method(a.prop[0], b.prop[0]);
    return {
      name: a.name,
      propOne: [diffA],
      propTwo: [diffB]
    };
  })
  // There must be a difference
  .filter(e => [e.propOne[0], e.propTwo[0]].some(e => Object.keys(e).length !== 0));

const destructive = compare(p1, p2, diffDestructive);
const constructive = compare(p1, p2, diffConstructive);

console.log(`Constructive method gives the wanted result: ${_.isEqual(result, destructive)}`);
console.log(`Destructive method gives the wanted result: ${_.isEqual(result, constructive)}`);
<!--
this is only for a deepequals function, _.isEqual,
and only used for checking the results. I could have copied
one into the code, but why make this even longer...
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.core.min.js"></script>