总结对象数组的频率

Summarize the frequency of array of objects

假设我有以下对象数组。

data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 },
  { x: 1, y: 2 },
  { x: 1, y: 1 }
]

我需要的是总结数组中相同对象的频率。输出将如下所示:

summary = [
  { x: 1, y: 1, f: 3 },
  { x: 1, y: 2, f: 1 },
  { x: 2, y: 2, f: 2 },
  { x: 3, y: 3, f: 1 }
]

现在我有这个代码

const summary = data.map((item, index, array) => {
  return { x: item.x, y: item.y, f: array.filter(i => i === item).length };
});

但我想我可以使用 reduceincludes 做得更好。有什么想法吗?

归约成一个对象,其键唯一代表一个对象,其值是该对象(具有 xyf 属性)。在每次迭代中,增加适当的键 f 属性,或者如果它不存在则在累加器上创建键:

const data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 },
  { x: 1, y: 2 },
  { x: 1, y: 1 }
];
const countObj = data.reduce((a, obj) => {
  const objString = obj.x + '_' + obj.y;
  if (!a[objString]) {
    a[objString] = { ...obj, f: 1 };
  } else {
    a[objString].f++;
  }
  return a;
}, {});
const output = Object.values(countObj);
console.log(output);

不要使用 map - 你最好像这样使用 reduce:

const summary = Object.values(data.reduce((a, { x, y }) => {
  a[`${x}-${y}`] = a[`${x}-${y}`] || { x, y, f: 0 };
  a[`${x}-${y}`].f++;
  return a;
}, {}));

基于Array#reduce的简单解决方案详述如下:

const data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 },
  { x: 1, y: 2 },
  { x: 1, y: 1 }
];

const summary = data.reduce((frequencySummary, item) => {
  
  /* Find a match for current item in current list of frequency summaries */
  const itemMatch = frequencySummary.find(i => i.x === item.x && i.y === item.y)
  
  if(!itemMatch) {
    
    /* If no match found, add a new item with inital frequency of 1 to the result */
    frequencySummary.push({ ...item, f : 1 });
  }
  else {
    
    /* If match found, increment the frequency count of that match */
    itemMatch.f ++;
  }
  
  return frequencySummary;

}, []);

console.log(summary)

我知道使用 reduce 可能更好,但我倾向于使用 forEach 和 findIndex 以获得更好的可读性。

var data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 },
  { x: 1, y: 2 },
  { x: 1, y: 1 }
];

var summary = [];

data.forEach(function(d){
  var idx = summary.findIndex(function(i){
    return i.x === d.x && i.y === d.y;
  });

  if(idx < 0){
    var sum = Object.assign({}, d);
    sum.f = 1;
    summary.push(sum);
  } else {
    summary[idx].f = summary[idx].f + 1;
  }
});

console.log(summary);

创建嵌套对象。外部对象使用 x 值作为键,嵌套对象包含 y 值作为键,值是频率。

data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 },
  { x: 1, y: 2 },
  { x: 1, y: 1 }
];

const nested = data.reduce((a, {x, y}) => {
  a[x] = a[x] || {};
  a[x][y] = a[x][y] ? a[x][y] + 1 : 1
  return a;
}, {});
const summary = [];
Object.keys(nested).forEach(x => Object.keys(nested[x]).forEach(y => summary.push({x, y, f: nested[x][y]})));

console.log(summary);

您可以使用 reduceMap,将 x 和 y 用作键,在每次迭代时检查地图上是否已经存在相同的键,而不仅仅是增加 f1 计数,如果不是,则将其设置为 1

const data = [{ x: 1, y: 1 },{ x: 2, y: 2 },{ x: 3, y: 3 },{ x: 2, y: 2 },{ x: 1, y: 1 },{ x: 1, y: 2 },{ x: 1, y: 1 }];

const countObj = data.reduce((a, obj) => {
  const objString = obj.x + '_' + obj.y;
  let value = a.get(objString) || obj
  let f = value && value.f  || 0
  a.set(objString, { ...value, f: f+1 })
  return a;
}, new Map());

console.log([...countObj.values()]);

Object.values(data.reduce((sum, i) => {
    i_str = JSON.stringify(i); // objects can't be keys
    sum[i_str] = Object.assign({}, i, {f: sum[i_str] ? sum[i_str].f+1 : 1});
    return sum;
}, {}));

注:

  1. 此代码段适用于任意对象的数组,只要它们是可字符串化的。
  2. 结果未排序,因为对象键未排序。如果这是一个问题,请随意排序。
  3. 你在做什么,是计算一​​个对象在数组中存在的次数。您可能想要对象外部的结果,而不是嵌入对象中的结果。沿着这些方向的东西可能更易于管理,返回对象描述到计数的映射:
data.reduce((sum, i) => {
    i_str = JSON.stringify(i); // objects can't be keys
    sum[i_str] = sum[i_str] ? sum[i_str]+1 : 1;
    return sum;
}, {});