如何将带有重复值的对象数组映射到具有标识符的数组?

4

处理像这样的对象数组:

const data = [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
];

我想要将一个数组映射为另一个数组,并添加一个额外的标识来识别重复的值:

const expected = [
  ["Car Wash Drops", 400],
  ["Personal/Seeding (1)", 48],
  ["Personal/Seeding (2)", 48],
];

到目前为止,我有一个映射函数来相应地映射值,但不确定如何处理仅针对重复项的标识符附加。

data.map(d => [`${d.value}`, d.count]);

结果为:

[
  ["Car Wash Drops", 400],
  ["Personal/Seeding", 48],
  ["Personal/Seeding", 48],
]

我也使用了索引,但是它会将索引添加到每个值上:

data.map((d, i) => [`${d.value} ${i}`, d.count]);

结果为:

[
  ["Car Wash Drops (0)", 400],
  ["Personal/Seeding (1)", 48],
  ["Personal/Seeding (2)", 48],
]

我建议澄清一下,您想要重复元素的原始数组索引,而不是元素出现次数的计数器。 - Shidersz
这个答案可能会有所帮助:https://dev59.com/U3E95IYBdhLWcg3wp_kg#36744732,因为这个问题已经在过去被问过并且有很多答案。 - J E Carter II
可能是 在 JavaScript 中从对象数组中删除重复项 的重复问题。 - J E Carter II
4个回答

3

使用您的方法,可以在 map 中使用 filter() 来检查原始数组中有多少个元素具有当前正在分析的元素的相同值,并根据此条件选择返回什么作为新值:

const data = [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
];

let res = data.map((x, idx) =>
{
    if (data.filter(y => y.value === x.value).length > 1)
        return [`${x.value} (${idx})`, x.count];
    else
        return [`${x.value}`, x.count];
});

console.log(res);

如果我们使用 some() 而不是 filter(),可以提高先前方法的性能,如下所示:

const data = [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
  {count: 300, value: "Operators/Management"},
  {count: 48, value: "Personal/Seeding"}
];

let res = data.map((x, idx) =>
{
    if (data.some((y, j) => y.value === x.value && idx !== j))
        return [`${x.value} (${idx})`, x.count];
    else
        return [`${x.value}`, x.count];
});

console.log(res);

如果我们事先创建一个Map来计算原始数组中每个元素出现的次数,则可以进一步提高性能。如下所示:

const data = [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
  {count: 300, value: "Operators/Management"},
  {count: 48, value: "Personal/Seeding"}
];

let counters = data.reduce((res, {value}) =>
{
    res.set(value, res.has(value) ? res.get(value) + 1 : 1);
    return res;
}, new Map());

let res = data.map((x, idx) =>
{
    return [
        `${x.value}` + (counters.get(x.value) > 1 ? `(${idx})` : ""),
        x.count
    ]; 
});

console.log(res);


1
需要注意的是,这是一个O(n^2)的过程,因此如果数组长度显着增加,它将变得昂贵。 - Pointy

1
尊重答案作者,但我认为有些地方可以在语法和性能上进行改进:
  1. 如果源数据非常大,则应避免使用reduce,虽然如果源数据相对较小(data.length<1000),我总是更喜欢使用reduce。尝试使用POF(普通for循环),因为它是最快的选项。

  2. ES6 Map 是处理键值对时的好帮手,但如果可能的话,我更喜欢POO(普通对象),因为它会使我们的代码更美观。

我将提供解决问题的方法(它将在最坏情况下使用额外的O(n)空间来存储键值对,它对源数据进行了两次扫描,因此第二次扫描是必需的,以便在第一次扫描时错过的位置放置1):

let data =  [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
  {count: 300, value: "Operators/Management"},
  {count: 48, value: "Personal/Seeding"}
];

const map = {};

for (let i=0;i<data.length;i+=1) {
  map[ data[i].value ] = map[ data[i].value ]+1 || 0;
  data[i].value = map[data[i].value]?data[i].value+` (${map[data[i].value]+1})`:data[i].value; 
}

for (let i=0;i<data.length;i+=1) {
  data[i].value = map[data[i].value]?data[i].value+` (1)`:data[i].value; 
}

console.log(data.map(o=>[o.value,o.count]));

或者使用 ES6 的 of 运算符[链接到 of]更加简洁:

let data =  [
      {count: 400, value: "Car Wash Drops"},
      {count: 48, value: "Personal/Seeding"},
      {count: 48, value: "Personal/Seeding"},
      {count: 300, value: "Operators/Management"},
      {count: 48, value: "Personal/Seeding"}
    ];

    const map = {};

    for (let d of data) {
      map[d.value] = map[d.value]+1 || 0;
      d.value = map[d.value]?d.value+` (${map[d.value]+1})`:d.value; 
    }

    for (let d of data) {
      d.value = map[d.value]?d.value+` (1)`:d.value; 
    }

    console.log(data.map(o=>[o.value,o.count]));


0
你可以维护一个从已经看到的值到看到它们的次数的映射。然后根据是否有重复项,更容易获得正确的数量:

const data = [
  {count: 400, value: "Car Wash Drops"},
  {count: 48, value: "Personal/Seeding"},
  {count: 48, value: "Personal/Seeding"},
];

let valueCounts = data.reduce((a, c) => {
  a[c.value] = a[c.value] || {current: 1, total: 0};
  a[c.value].total += 1;
  return a;
}, {});

const expected = data.map(({count, value}) => {
  if (valueCounts[value].total === 1) return [value, count];
  return [`${value} (${valueCounts[value].current++})`, count];
});

console.log(expected);


-1

在回调函数之后,您可以向.map()传递第二个参数。当调用回调函数时,该值将用作this的值。因此,您可以传递一个对象,以便累加重复次数:

data.map(function(d, i) {
  if (!(d.value in this))
    this[d.value] = 0;
  else
    this[d.value] += 1;

  return [
    d.value + (this[d.value] ? " (" + this[d.value] + ")" : ""),
    d.count
  ];
}, {});

通过从一个空对象开始,回调函数可以跟踪它已经看到每个 d.value 字符串的次数。当它看到重复时,它可以将限定词添加到字符串中。
现在,这并不完全符合您的要求,因为它只查看一个值。因此,第一次看到 "Personal/Seeding" 时,它不知道它是重复的,因此不会用限定词修改它。当然,在第二次遍历时会进行修改。
如果这样做还不够好,那么按照您的要求需要先完整地遍历数组,以获取每个字符串的重复计数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接