在工作期间,我承担了以下任务:将数组中具有相似属性的元素分组。

通常,问题如下: br />如果我将这些元素按lastnameage分组,则会得到以下结果:

var list = [
    {name: "1", lastname: "foo1", age: "16"},
    {name: "2", lastname: "foo", age: "13"},
    {name: "3", lastname: "foo1", age: "11"},
    {name: "4", lastname: "foo", age: "11"},
    {name: "5", lastname: "foo1", age: "16"},
    {name: "6", lastname: "foo", age: "16"},
    {name: "7", lastname: "foo1", age: "13"},
    {name: "8", lastname: "foo1", age: "16"},
    {name: "9", lastname: "foo", age: "13"},
    {name: "0", lastname: "foo", age: "16"}
];


经过一番试验,我得出以下解决方案: >
var result = [
    [
        {name: "1", lastname: "foo1", age: "16"},
        {name: "5", lastname: "foo1", age: "16"}, 
        {name: "8", lastname: "foo1", age: "16"}
    ],
    [
        {name: "2", lastname: "foo", age: "13"},
        {name: "9", lastname: "foo", age: "13"}
    ],
    [
        {name: "3", lastname: "foo1", age: "11"}
    ],
    [
        {name: "4", lastname: "foo", age: "11"}
    ],
    [
        {name: "6", lastname: "foo", age: "16"},
        {name: "0", lastname: "foo", age: "16"}
    ],
    [
        {name: "7", lastname: "foo1", age: "13"}
    ]         
];


此解决方案有效,但这是正确的最佳方法吗?在我看来还是有点难看。

评论

有一个名为group-array的npm模块可以满足相同的要求。

为什么不仅仅根据这些值对数组排序?

#1 楼

我不得不写信,您可能应该将forEach和map与Alexey Lebedev的答案结合起来。

function groupBy( array , f )
{
  var groups = {};
  array.forEach( function( o )
  {
    var group = JSON.stringify( f(o) );
    groups[group] = groups[group] || [];
    groups[group].push( o );  
  });
  return Object.keys(groups).map( function( group )
  {
    return groups[group]; 
  })
}

var result = groupBy(list, function(item)
{
  return [item.lastname, item.age];
});


#2 楼

函数的主要问题是在最坏的情况下二次时间复杂度。另外,如果我们首先实现通用的groupBy函数,则按属性分组将变得微不足道。 t禁止扩展对象原型。

评论


\ $ \ begingroup \ $
也许您应该考虑将序列化函数传递给groupBy方法。对于toString,存在几个问题:item1 = {lastName:[1,2],age:3}和item2 = {lastName:1,age:[2,3]} and item3 = {lastName:'1,2', age:3}将全部放在您的示例的同一组中。
\ $ \ endgroup \ $
– Tibos
2013年12月11日10:40



\ $ \ begingroup \ $
Tibos:是的,那是一个等待发生的错误。我已经将toString()更改为JSON.stringify()。假设JSON是本地实现的,则速度是可比的。
\ $ \ endgroup \ $
–阿列克谢·列别杰夫(Alexey Lebedev)
2013年12月11日12:16

\ $ \ begingroup \ $
使用stringify作为键非常出色,+ 1
\ $ \ endgroup \ $
– konijn
2013年12月11日13:38

\ $ \ begingroup \ $
@AlexeyLebedev提出了出色的解决方案!假设我要小写所以忽略大小写怎么办?
\ $ \ endgroup \ $
– loretoparisi
19年2月27日在19:39

\ $ \ begingroup \ $
@loretoparisi return [item.lastname.toLowerCase(),item.age];
\ $ \ endgroup \ $
–阿列克谢·列别杰夫(Alexey Lebedev)
19年2月27日在20:01

#3 楼

我发现JavaScript的功能方面是一个很大的优势。当涉及到循环时,Array.prototype.forEach和表兄弟可以帮助您的代码更具描述性:对象(在这种情况下为Array.prototype),我留了解决方案的那部分。但是,我将groupByProperties添加为Array.prototype的不可枚举属性,因此它不会出现在for..in枚举中。

评论


\ $ \ begingroup \ $
太好了!但是一些测试表明,forEach方法比具有预定义长度的for循环要慢得多。你不同意吗? =)
\ $ \ endgroup \ $
–赛克
2013年12月10日13:03



\ $ \ begingroup \ $
的确,forEach可能比for循环慢大约5倍。它的运行速度仍然相当快,我热衷于清晰地编写代码,并在需要时优化瓶颈。
\ $ \ endgroup \ $
– Tibos
2013年12月10日13:38

\ $ \ begingroup \ $
同样的问题是创建一个局部变量来存储您要遍历的数组的长度。访问局部变量的速度可能比array.length快得多,但是当您进行迭代时,这并不是瓶颈,因此具有已保存变量的循环与具有array.length的常规循环一样快。 (该规则的例外是,当length不是静态属性,而是像实时NodeList一样动态计算时)。
\ $ \ endgroup \ $
– Tibos
2013年12月10日14:10



#4 楼

另一种方法是使用_lodash.groupBy或_lodash.keyBy:



您只需编写几行代码即可获得相同的结果:

const Results = _.groupBy(list, 'lastname')


这将按姓氏对结果进行分组。但是,根据您的情况,您需要按多个属性进行分组-您可以使用此代码段来对该函数进行附魔。

当然您可以多次使用此代码。
Lodash允许您安装其代码一对一的模块(npm i lodash.groupby);

我相信这样您将获得更短,更易维护的代码,并且功能清晰。我想这是另一种选择。

评论


\ $ \ begingroup \ $
欢迎使用代码审查!您提出了替代解决方案,但尚未检查代码。请说明您的推理(您的解决方案如何工作以及如何对原始解决方案进行改进),以便作者可以从您的思考过程中学习。
\ $ \ endgroup \ $
– Pimgd
16 Mar 3 '16 at 10:04

\ $ \ begingroup \ $
可能不是代码解决方案,但是除了“简单的代码”(它什么也没有解释)之外,您现在对为什么使用这些功能没有任何解释。
\ $ \ endgroup \ $
– Pimgd
16 Mar 3 '16 at 10:05