使用对象对数组项进行分组

Posted

技术标签:

【中文标题】使用对象对数组项进行分组【英文标题】:Group array items using object 【发布时间】:2022-01-22 12:38:12 【问题描述】:

我的数组是这样的:

myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black"
]

我想把它转换成:

myArray = [
  group: "one", color: ["red", "green", "black"]
  group: "two", color: ["blue"]
]

所以,基本上,按group 分组。

我正在尝试:

for (i in myArray)
  var group = myArray[i].group;
  //myArray.push(group, ???)

我只是不知道如何处理相似组值的分组。

【问题讨论】:

你有没有尝试过?已经有许多与 SO 密切相关的问题。请参阅this、this 和 this。 那里有很多语法错误。请在发布之前测试您的代码。 【参考方案1】:

首先,在 javascript 中,使用for ... in 遍历数组通常不是一个好主意。详情请见Why is using "for...in" with array iteration a bad idea?。

所以你可以试试这样的:

var groups = ;
for (var i = 0; i < myArray.length; i++) 
  var groupName = myArray[i].group;
  if (!groups[groupName]) 
    groups[groupName] = [];
  
  groups[groupName].push(myArray[i].color);

myArray = [];
for (var groupName in groups) 
  myArray.push(group: groupName, color: groups[groupName]);

在此处使用中介 groups 对象有助于加快处理速度,因为它允许您避免嵌套循环来搜索数组。另外,因为groups 是一个对象(而不是数组),所以使用for ... in 对其进行迭代是合适的。

附录

FWIW,如果您想避免结果数组中出现重复的颜色条目,您可以在 groups[groupName].push(myArray[i].color); 行上方添加 if 语句以防止重复。使用 jQuery 它看起来像这样;

if (!$.inArray(myArray[i].color, groups[groupName])) 
  groups[groupName].push(myArray[i].color);

如果没有 jQuery,您可能想要添加一个与 jQuery 的 inArray 做同样事情的函数:

Array.prototype.contains = function(value) 
  for (var i = 0; i < this.length; i++) 
    if (this[i] === value)
      return true;
  
  return false;

然后像这样使用它:

if (!groups[groupName].contains(myArray[i].color)) 
  groups[groupName].push(myArray[i].color);

请注意,在任何一种情况下,由于所有额外的迭代,您都会稍微放慢速度,因此如果您不需要避免结果数组中出现重复的颜色条目,我建议您避免使用这些额外的代码。那里

【讨论】:

很抱歉,如果你在_myArray_. With var myArray = [ group: "one", color: "red", group: "two", color: "blue", group: "one", color: "green", group: "one", color: "black", group: "one", color: "black" ]; 中有两个相同的对象,它就不起作用你会得到myArray[0].color = ["red", "green", "black", "black"] “不起作用”是主观的。 OP 从未说明在这种情况下该怎么做。 @Lends OP 从未将其指定为要求。事实上,鉴于 OP 的评论,我会说这个解决方案确实“有效”。 @neuronaut 对不起,但也许作者只是没有检查它,除了数组中对象的格式之外没有任何要求。没关系,你的答案已经得到证实,我不会试图评判你。但是,如果您有时间来纠正这个错误,那就太好了,这样其他人就可以使用您的代码而不是复制这个主题。谢谢! 发布的解决方案满足了要求,问题没有说明如何处理重复项。未定义问题的“修复”甚至可能会让未来的读者更加困惑,因为他们不期望该功能。【参考方案2】:

一个选项是:

var res = myArray.reduce(function(groups, currentValue) 
    if ( groups.indexOf(currentValue.group) === -1 ) 
      groups.push(currentValue.group);
    
    return groups;
, []).map(function(group) 
    return 
        group: group,
        color: myArray.filter(function(_el) 
          return _el.group === group;
        ).map(function(_el)  return _el.color; )
    
);

http://jsfiddle.net/dvgwodxq/

【讨论】:

非常优雅和独立,很好。我唯一的抱怨是它不像更简单的 for 循环那样立即可读。 reduce、indexOf、map、filter、map 对我来说似乎过于复杂。 @KingMob 定义“复杂”。这些是常规的数组方法。 它可以工作,但我需要一些时间来浏览代码并理解它。非常感谢您的帮助! @NunoNogueira 非常欢迎您。这只是其中一种选择。【参考方案3】:

这个版本利用了对象键是唯一的。我们处理原始数组并在新对象中按组收集颜色。然后从该组创建新对象 -> 颜色数组映射。

var myArray = [
      group: "one",
      color: "red"
    , 
      group: "two",
      color: "blue"
    , 
      group: "one",
      color: "green"
    , 
      group: "one",
      color: "black"
    ];

    //new object with keys as group and
    //color array as value
    var newArray = ;

    //iterate through each element of array
    myArray.forEach(function(val) 
      var curr = newArray[val.group]

      //if array key doesnt exist, init with empty array
      if (!curr) 
        newArray[val.group] = [];
      

      //append color to this key
      newArray[val.group].push(val.color);
    );

    //remove elements from previous array
    myArray.length = 0;

    //replace elements with new objects made of
    //key value pairs from our created object
    for (var key in newArray) 
      myArray.push(
        'group': key,
        'color': newArray[key]
      );
    

请注意,这不考虑同一组的重复颜色,因此单个组的数组中可能有多个相同的颜色。

【讨论】:

【参考方案4】:

你可以这样做:

function convert(items) 
    var result = [];

    items.forEach(function (element) 
        var existingElement = result.filter(function (item) 
            return item.group === element.group;
        )[0];

        if (existingElement) 
            existingElement.color.push(element.color);
         else 
            element.color = [element.color];
            result.push(element);
        

    );

    return result;

【讨论】:

【参考方案5】:

首先创建组名到值的映射。 然后转换成你想要的格式。

var myArray = [
    group: "one", color: "red",
    group: "two", color: "blue",
    group: "one", color: "green",
    group: "one", color: "black"
];

var group_to_values = myArray.reduce(function (obj, item) 
    obj[item.group] = obj[item.group] || [];
    obj[item.group].push(item.color);
    return obj;
, );

var groups = Object.keys(group_to_values).map(function (key) 
    return group: key, color: group_to_values[key];
);

var pre = document.createElement("pre");
pre.innerhtml = "groups:\n\n" + JSON.stringify(groups, null, 4);
document.body.appendChild(pre);

使用reduce 和map 等数组实例方法为您提供了强大的高级构造,可以为您省去很多手动循环的痛苦。

【讨论】:

很好的答案。使用 Object.entries 和 ES6 我们也可以做到groups = Object.entries(group_to_values).map(([group, color]) =&gt; ( group, color )); 太棒了!我的问题是使用Object.keys 拉动键​​以形成对象数组 如果我的原始数组看起来像 group: "one", color: "red", size:"big",我将如何在最终对象中获取另一个元素?【参考方案6】:

使用 lodash 的 groupby 方法

创建一个由通过迭代器运行集合的每个元素的结果生成的键组成的对象。分组值的顺序由它们在集合中出现的顺序决定。每个键对应的值是负责生成键的元素数组。使用一个参数调用迭代对象:(值)。

因此,使用 lodash,您可以在一行中获得所需的内容。见下文

let myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black",
]
let grouppedArray=_.groupBy(myArray,'group')
console.log(grouppedArray)
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"&gt;&lt;/script&gt;

【讨论】:

结果不是需要的数据结构【参考方案7】:

除了使用两遍方法的给定方法外,如果找到新组,您可以通过推送组来采用单循环方法。

var array = [ group: "one", color: "red" ,  group: "two", color: "blue" ,  group: "one", color: "green" ,  group: "one", color: "black" ],
    groups = Object.create(null),
    grouped = [];

array.forEach(function (o) 
    if (!groups[o.group]) 
        groups[o.group] = [];
        grouped.push( group: o.group, color: groups[o.group] );
    
    groups[o.group].push(o.color);
);

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

【讨论】:

【参考方案8】:
var array = [
      id: "123",
      name: "aaaaaaaa"
    , 
      id: "123",
      name: "aaaaaaaa"
    , 
      id: '456',
      name: 'bbbbbbbbbb'
    , 
      id: '789',
      name: 'ccccccccc'
    , 
      id: '789',
      name: 'ccccccccc'
    , 
      id: '098',
      name: 'dddddddddddd'
    ];
//if you want to group this array
group(array, key) 
  console.log(array);
  let finalArray = [];
  array.forEach(function(element) 
    var newArray = [];
    array.forEach(function(element1) 
      if (element[key] == element1[key]) 
          newArray.push(element)
      
    );
    if (!(finalArray.some(arrVal => newArray[0][key] == arrVal[0][key]))) 
        finalArray.push(newArray);
    
  );
  return finalArray

//and call this function
groupArray(arr, key) 
  console.log(this.group(arr, key))

【讨论】:

【参考方案9】:

使用 Array 的 reducefindIndex 方法,可以实现这一点。

var myArray = [
  group: "one",
  color: "red"
, 
  group: "two",
  color: "blue"
, 
  group: "one",
  color: "green"
, 
  group: "one",
  color: "black"
];

var transformedArray = myArray.reduce((acc, arr) => 
  var index = acc.findIndex(function(element) 
    return element.group === arr.group;
  );
  if (index === -1) 
    return acc.push(
      group: arr.group,
      color: [arr.color]
    );
  
  
  acc[index].color.push(arr.color);
  return acc;
, []);

console.log(transformedArray);

通过使用reduce函数,数组是迭代器,新值存储在acc (accumulating)参数中。要检查给定group 的对象是否已经存在,我们可以使用findIndex 函数。

如果findIndex()返回-1,则该值不存在,所以在acc参数中添加数组。

如果findIndex() 返回索引,则使用arr 值更新index

【讨论】:

【参考方案10】:

另一个选项是使用reduce()new Map() 对数组进行分组。使用Spread syntax将集合对象转换为数组。

var myArray = ["group":"one","color":"red","group":"two","color":"blue","group":"one","color":"green","group":"one","color":"black"]

var result = [...myArray.reduce((c, group,color) => 
  if (!c.has(group)) c.set(group, group,color: []);
  c.get(group).color.push(color);
  return c;
, new Map()).values()];

console.log(result);

【讨论】:

【参考方案11】:

myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black"
];


let group = myArray.map((item)=>  item.group ).filter((item, i, ar) => ar.indexOf(item) === i).sort((a, b)=> a - b).map(item=>
    let new_list = myArray.filter(itm => itm.group == item).map(itm=>itm.color);
    return group:item,color:new_list
);
console.log(group);

【讨论】:

【参考方案12】:

您可以使用下一个扩展数组功能:

Array.prototype.groupBy = function(prop) 
  var result = this.reduce(function (groups, item) 
      const val = item[prop];
      groups[val] = groups[val] || [];
      groups[val].push(item);
      return groups;
  , );
  return Object.keys(result).map(function(key) 
      return result[key];
  );
;

使用示例:

/* re-usable function */
Array.prototype.groupBy = function(prop) 
  var result = this.reduce(function (groups, item) 
      const val = item[prop];
      groups[val] = groups[val] || [];
      groups[val].push(item);
      return groups;
  , );
  return Object.keys(result).map(function(key) 
      return result[key];
  );
;

var myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black"
]

console.log(myArray.groupBy('group'));

致谢:@Wahinya Brian

【讨论】:

【参考方案13】:

试试 (h=)

myArray.forEach(x=> h[x.group]= (h[x.group]||[]).concat(x.color) );
myArray = Object.keys(h).map(k=> (group:k, color:h[k]))

let myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black",
];

let h=;

myArray.forEach(x=> h[x.group]= (h[x.group]||[]).concat(x.color) );
myArray = Object.keys(h).map(k=> (group:k, color:h[k]))

console.log(myArray);

【讨论】:

【参考方案14】:

我使用减速器的方法:

myArray = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black"
]

console.log(myArray.reduce( (acc, curr) => 
  const itemExists = acc.find(item => curr.group === item.group)
  if(itemExists)
    itemExists.color = [...itemExists.color, curr.color]
  else
    acc.push(group: curr.group, color: [curr.color])
  
  return acc;
, []))

【讨论】:

【参考方案15】:

如果您不想重复颜色值,这将为您提供独特的颜色

var arr = [
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "red",
  group: "two", color: "blue",
  group: "one", color: "green",
  group: "one", color: "black"
]

var arra = [...new Set(arr.map(x => x.group))]

let reformattedArray = arra.map(obj => 
   let rObj = 
   rObj['color'] = [...new Set(arr.map(x => x.group == obj ? x.color:false ))]
       .filter(x => x != false)
   rObj['group'] = obj
   return rObj
)
console.log(reformattedArray)

【讨论】:

【参考方案16】:

这个 repo 提供了 lodash 中的解决方案和原生 Js 中的替代方案,您可以找到如何实现 groupby。 https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_groupby

【讨论】:

这很有帮助,但我想我会使用 lodash 方法,因为它更容易阅读!【参考方案17】:

使用 ES6,这可以很好地完成,使用 .reduce()Map 作为累加器,然后使用 Array.from() 及其映射函数将每个分组的映射条目映射到一个对象:

const arr = ["group":"one","color":"red","group":"two","color":"blue","group":"one","color":"green","group":"one","color":"black"];

const res = Array.from(arr.reduce((m, group, color) => 
    m.set(group, [...(m.get(group) || []), color]), new Map
  ), ([group, color]) => (group, color)
);

console.log(res);

如果您的对象中除了 groupcolor 之外还有其他属性,则可以采用更通用的方法,将分组对象设置为地图的值,如下所示:

const arr = ["group":"one","color":"red","group":"two","color":"blue","group":"one","color":"green","group":"one","color":"black"];

const groupAndMerge = (arr, groupBy, mergeInto) => 
  Array.from(arr.reduce((m, o) => 
    const curr = m.get(o[groupBy]);
    return m.set(o[groupBy], ...o, [mergeInto]: [...(curr && curr[mergeInto] || []), o[mergeInto]]);
  , new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));

如果你能支持optional chaining和nullish coalescing operator (??),你可以把上面的方法简化成如下:

const arr = ["group":"one","color":"red","group":"two","color":"blue","group":"one","color":"green","group":"one","color":"black"];
const groupAndMerge = (arr, groupBy, mergeWith) =>
  Array.from(arr.reduce((m, o) => m.set(o[groupBy], ...o, [mergeWith]: [...(m.get(o[groupBy])?.[mergeWith] ?? []), o[mergeWith]]), new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));

【讨论】:

【参考方案18】:

我喜欢使用Map 构造函数回调来创建组(映射键)。第二步是填充该地图的值,最后以所需的输出格式提取地图的数据:

let myArray = [group: "one", color: "red",group: "two", color: "blue",
               group: "one", color: "green",group: "one", color: "black"];

let map = new Map(myArray.map((group) => [group,  group, color: [] ]));
for (let group, color of myArray) map.get(group).color.push(color);
let result = [...map.values()];

console.log(result);

 

【讨论】:

以上是关于使用对象对数组项进行分组的主要内容,如果未能解决你的问题,请参考以下文章

基于变量 javascript 对数组项进行分组

按自定义时间间隔对对象数组进行分组

对对象数组进行分组的最有效方法

按id typescript对对象数组进行分组[重复]

Eloquent 按碳日期周数对数组进行分组

如何对对象数据的mongodb聚合数组进行分组以对同一日期的数字求和