Javascript 按两个属性对 JSON 对象进行分组并计数

Javascript group a JSON object by two properties and count

我有一个这样的 JSON 对象:

long_array =    
[
{"location":"Kirrawee","identity_long":"student"},
{"location":"Kirrawee","identity_long":"visitor"},
{"location":"Kirrawee","identity_long":"visitor"},
{"location":"Kirrawee","identity_long":"worker"},
{"location":"Sutherland","identity_long":"student"},
{"location":"Sutherland","identity_long":"resident"},
{"location":"Sutherland","identity_long":"worker"},
{"location":"Sutherland","identity_long":"resident"},
{"location":"Miranda","identity_long":"resident"},
{"location":"Miranda","identity_long":"worker"},
{"location":"Miranda","identity_long":"student"},
{"location":"Miranda","identity_long":""},
{"location":"Miranda","identity_long":"worker"},
{"location":"Miranda","identity_long":"resident"}
];

我想要实现的是一个像下面这样的对象:

grouped_and_counted_location_and_identity =
[
{"location":"Kirrawee","identity":"student","count":1},
{"location":"Kirrawee","identity":"visitor","count":2},
{"location":"Kirrawee","identity":"worker","count":1},
{"location":"Sutherland","identity":"student","count":1},
{"location":"Sutherland","identity":"resident","count":2},
{"location":"Sutherland","identity":"worker","count":1},
{"location":"Miranda","identity":"resident","count":2},
{"location":"Miranda","identity":"worker","count":2},
{"location":"Miranda","identity":"student","count":1}
];

我发现这在 R 语言中非常容易实现,我会这样做:

long_array %>%
    group_by(location, identity_long) %>%
    summarise(n = n())

甚至只是

long_array %>%
    count(location, identity_long)

但是我怎样才能在 javascript 中做到这一点?

我只想按两个属性对 JSON 对象进行分组,并计算相同的出现次数。

创建数组的一组唯一值并将其与 long_array 中存在的所有值进行比较,计算相同的值并将其保存在新数组中

const long_array = [{
    "location": "Kirrawee",
    "identity_long": "student"
  },
  {
    "location": "Kirrawee",
    "identity_long": "visitor"
  },
  {
    "location": "Kirrawee",
    "identity_long": "visitor"
  },
  {
    "location": "Kirrawee",
    "identity_long": "worker"
  },
  {
    "location": "Sutherland",
    "identity_long": "student"
  },
  {
    "location": "Sutherland",
    "identity_long": "resident"
  },
  {
    "location": "Sutherland",
    "identity_long": "worker"
  },
  {
    "location": "Sutherland",
    "identity_long": "resident"
  },
  {
    "location": "Miranda",
    "identity_long": "resident"
  },
  {
    "location": "Miranda",
    "identity_long": "worker"
  },
  {
    "location": "Miranda",
    "identity_long": "student"
  },
  {
    "location": "Miranda",
    "identity_long": ""
  },
  {
    "location": "Miranda",
    "identity_long": "worker"
  },
  {
    "location": "Miranda",
    "identity_long": "resident"
  }
];
const unique_values = [...new Map(long_array.map(obj => [JSON.stringify(obj), obj])).values()]; // this will remove duplicate values from long_array
const result = unique_values.map((val) => { // iterate in unique_values
  let count = 0;
  long_array.forEach((item) => {
    item.location == val.location && item.identity_long === val.identity_long && count++
  }); //iterate in long_array and count same values
  return { ...val,
    count: count
  }
})
console.log(result);

解决此问题的一种方法是将对象转换为字符串(使用 JSON.stringify). Since comparing objects in JavaScript compares the instance, and not the fields/values, converting to strings would make it easier to identify duplicates. You could then do something like this 之类的方法来计算重复项!希望这会有所帮助!

您可以使用 lodash 等库并使用它的按功能分组来完成这种简单的方法,有点耗时的方法是实现您自己的按功能分组。

let long_array = [{
    "location": "Kirrawee",
    "identity_long": "student"
  },
  {
    "location": "Kirrawee",
    "identity_long": "visitor"
  },
  {
    "location": "Kirrawee",
    "identity_long": "visitor"
  },
  {
    "location": "Kirrawee",
    "identity_long": "worker"
  },
  {
    "location": "Sutherland",
    "identity_long": "student"
  },
  {
    "location": "Sutherland",
    "identity_long": "resident"
  },
  {
    "location": "Sutherland",
    "identity_long": "worker"
  },
  {
    "location": "Sutherland",
    "identity_long": "resident"
  },
  {
    "location": "Miranda",
    "identity_long": "resident"
  },
  {
    "location": "Miranda",
    "identity_long": "worker"
  },
  {
    "location": "Miranda",
    "identity_long": "student"
  },
  {
    "location": "Miranda",
    "identity_long": ""
  },
  {
    "location": "Miranda",
    "identity_long": "worker"
  },
  {
    "location": "Miranda",
    "identity_long": "resident"
  }
];

function addItemCounts(items, groupByKeys) {
  var groups = _.groupBy(long_array, obj => {
    return groupByKeys.map(key => obj[key]).join('-');
  });

  return _.map(groups, g => ({
    ...g[0],
    count: g.length
  }));
}

console.log(addItemCounts(long_array, ['identity_long', 'location']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>

这是一个解决方案,时间复杂度为 O(n*logn),space 复杂度为 O(n)

let long_array =
  [
    { "location": "Kirrawee", "identity_long": "student" },
    { "location": "Kirrawee", "identity_long": "visitor" },
    { "location": "Kirrawee", "identity_long": "visitor" },
    { "location": "Kirrawee", "identity_long": "worker" },
    { "location": "Sutherland", "identity_long": "student" },
    { "location": "Sutherland", "identity_long": "resident" },
    { "location": "Sutherland", "identity_long": "worker" },
    { "location": "Sutherland", "identity_long": "resident" },
    { "location": "Miranda", "identity_long": "resident" },
    { "location": "Miranda", "identity_long": "worker" },
    { "location": "Miranda", "identity_long": "student" },
    { "location": "Miranda", "identity_long": "" },
    { "location": "Miranda", "identity_long": "worker" },
    { "location": "Miranda", "identity_long": "resident" }
  ];

// create map
let map = new Map()

for (let i = 0; i < long_array.length; i++) {
  const s = JSON.stringify(long_array[i])

  if (!map.has(s)) {
    // if the map does not contain the object already
    // i.e. its first occurrence
    map.set(s, {
      location: long_array[i].location,
      identity: long_array[i].identity_long,
      count: 1,
    })
  } else {
    // if it no first occurrence
    // increase the count straight way
    map.get(s).count++
  }
}

const result = Array.from(map.values())
console.log(result)

这是我设法想出的,现在我看到了其他答案,它非常复杂,但我认为它有效:

const grouped_and_counted_location_and_identity = [];

const sort = () => {
  long_array.forEach((el) => {
    let count = 0;
    for (let i = long_array.indexOf(el); i < long_array.length; i++) {
      if (
        el.location == long_array[i].location &&
        el.identity_long == long_array[i].identity_long
      ) {
        count++;
      }
    }
    el.count = count;
    if (
      !grouped_and_counted_location_and_identity.some(
        (elem) =>
          elem.location == el.location && elem.identity_long == el.identity_long
      )
    ) {
      grouped_and_counted_location_and_identity.push(el);
    }
  });
};

sort();

console.log(grouped_and_counted_location_and_identity);

  1. 根据包含 locationidentity_long 的字符串创建计数映射:

    { Kirrawee-student: 1, ... }
    
  2. 映射每个条目并将其转换为所需的结构。

const long_array = [
  { "location": "Kirrawee", "identity_long": "student" },
  { "location": "Kirrawee", "identity_long": "visitor" },
  { "location": "Kirrawee", "identity_long": "visitor" },
  { "location": "Kirrawee", "identity_long": "worker" },
  { "location": "Sutherland", "identity_long": "student" },
  { "location": "Sutherland", "identity_long": "resident" },
  { "location": "Sutherland", "identity_long": "worker" },
  { "location": "Sutherland", "identity_long": "resident" },
  { "location": "Miranda", "identity_long": "resident" },
  { "location": "Miranda", "identity_long": "worker" },
  { "location": "Miranda", "identity_long": "student" },
  { "location": "Miranda", "identity_long": "" },
  { "location": "Miranda", "identity_long": "worker" },
  { "location": "Miranda", "identity_long": "resident" }
];

const countsMap = _.countBy(long_array, e => `${e.location}-${e.identity_long}`);
const longArrayWithCount = Object.entries(countsMap).map(([group, count]) => {
 const [location, identity_long] = group.split('-');

 return { location, identity_long, count };
});

console.log(longArrayWithCount);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>

这可以使用 Array.reduce and Object.values along with Optional Chaining

来实现

let long_array = [{location:'Kirrawee',identity_long:'student'},{location:'Kirrawee',identity_long:'visitor'},{location:'Kirrawee',identity_long:'visitor'},{location:'Kirrawee',identity_long:'worker'},{location:'Sutherland',identity_long:'student'},{location:'Sutherland',identity_long:'resident'},{location:'Sutherland',identity_long:'worker'},{location:'Sutherland',identity_long:'resident'},{location:'Miranda',identity_long:'resident'},{location:'Miranda',identity_long:'worker'},{location:'Miranda',identity_long:'student'},{location:'Miranda',identity_long:''},{location:'Miranda',identity_long:'worker'},{location:'Miranda',identity_long:'resident'}];

const formatData = (data) => {
  const finalRes = data.reduce((res, {location, identity_long}) => {
  //Formatting a composite key using `location` and `identity_long`
    let key = `${location}_${identity_long}`;
    res[key] = {
      ...res[key],
      location,
      identity: identity_long,
      //Accessing the count form the result/accumulator if exists and if not assigning `0` and updating by 1
      count: (res[key]?.count || 0) + 1
    }
    return res;
  }, {});
  //Finally returning the values of the object 
  return Object.values(finalRes)
}

console.log(formatData(long_array))
.as-console-wrapper {
  max-height: 100% !important;
}