在案例类列表上进行聚合的更好方法

时间:2016-03-09 19:11:12

标签: scala case-class foldleft

我有案例类列表。输出需要对案例类的不同参数进行聚合。寻找更优化的方法来做到这一点。

示例:

case class Students(city: String, college: String, group: String,
                    name: String, fee: Int, age: Int)

object GroupByStudents {
  val studentsList= List(
    Students("Mumbai","College1","Science","Jony",100,30),
    Students("Mumbai","College1","Science","Tony", 200, 25),
    Students("Mumbai","College1","Social","Bony",250,30),
    Students("Mumbai","College2","Science","Gony", 240, 28),
    Students("Bangalore","College3","Science","Hony", 270, 28))
}

现在要了解一个城市学生的详细信息,我需要首先按城市汇总,然后根据大学明智地分解这些细节,然后分组。

输出是以下格式的案例类列表。

Students(Mumbai,,,,790,0) -- aggregate city wise
Students(Mumbai,College1,,,550,0)  -- aggregate college wise
Students(Mumbai,College1,Social,,250,0)
Students(Mumbai,College1,Science,,300,0)
Students(Mumbai,College2,,,240,0)
Students(Mumbai,College2,Science,,240,0)
Students(Bangalore,,,,270,0)
Students(Bangalore,College3,,,270,0)
Students(Bangalore,College3,Science,,270,0)

实现此目的的两种方法:

1)循环所有列表,为每个组合创建一个地图(以上情况3组合 ),聚合数据并创建新的结果列表并将数据附加到其中。

2)使用foldLeft选项

studentsList.groupBy(d=>(d.city))
  .mapValues(_.foldLeft(Students("","","","",0,0))
    ((r,c) => Students(c.city,"","","",r.fee+c.fee,0)))

studentsList.groupBy(d=>(d.city,d.college))
  .mapValues(_.foldLeft(Students("","","","",0,0))
    ((r,c) => Students(c.city,c.college,"","",r.fee+c.fee,0)))

studentsList.groupBy(d=>(d.city,d.college,d.group))
  .mapValues(_.foldLeft(Students("","","","",0,0))
    ((r,c) => Students(c.city,c.college,c.group,"",r.fee+c.fee,0)))

在这两种情况下,不止一次循环列表。有没有办法通过单程和优化的方式实现这一目标。

2 个答案:

答案 0 :(得分:2)

使用GroupBy

代码看起来更好一些,但我认为它并不快。使用groupby,你总是有2个“循环”

studentsList.groupBy(d=>(d.city)).map { case (k,v) =>
    Students(v.head.city,"","","",v.map(_.fee).sum, 0)
}
studentsList.groupBy(d=>(d.city,d.college)).map { case (k,v) =>
    Students(v.head.city,v.head.college,"","",v.map(_.fee).sum, 0)
}    
studentsList.groupBy(d=>(d.city,d.college,d.group)).map { case (k,v) =>
    Students(v.head.city,v.head.college,v.head.group,"",v.map(_.fee).sum, 0)
}

你会得到像这样的东西

List(Students(Bangalore,College3,Science,Hony,270,0),
     Students(Mumbai,College1,Science,Jony,790,0))
List(Students(Mumbai,College2,,,240,0),
     Students(Bangalore,College3,,,270,0),  
     Students(Mumbai,College1,,,550,0))
List(Students(Bangalore,College3,Science,,270,0), 
     Students(Mumbai,College2,Science,,240,0), 
     Students(Mumbai,College1,Social,,250,0), 
     Students(Mumbai,College1,Science,,300,0))

它与您的示例中的输出不完全相同,但它是所需的输出:案例类学生列表。

用于理解

如果你自己分组,你可以避免这种循环。只有城市的例子,另一个是直接的。

var m = Map[String, Students]()
for (v <- studentsList) {
    m += v.city -> Students(v.city,"","","",v.fee + m.getOrElse(v.city, Students("","","","",0,0)).asInstanceOf[Students].fee, 0)
}
m

<强>输出

与studenList相同的输出,但我只为每个Map[String,Students]输出循环一次。

Map(Mumbai -> Students(Mumbai,,,,790,0), Bangalore -> Students(Bangalore,,,,270,0))

使用Foldleft

只需在完整列表中循环一遍。

val emptyStudent = Students("","","","",0,0);
studentsList.foldLeft(Map[String, Students]()) { case (m, v) =>
    m + (v.city -> Students(v.city,"","","",
                            v.fee + m.getOrElse(v.city, emptyStudent).fee, 0))
}
studentsList.foldLeft(Map[(String,String), Students]()) { case (m, v) =>
    m + ((v.city,v.college) -> Students(v.city,v.college,"","",
                                        v.fee + m.getOrElse((v.city,v.college), emptyStudent).fee, 0))
}
studentsList.foldLeft(Map[(String,String,String), Students]()) { case (m, v) =>
    m + ((v.city,v.college,v.group) -> Students(v.city,v.college,v.group,"",
                                                v.fee + m.getOrElse((v.city,v.college,v.group), emptyStudent).fee, 0))
}

<强>输出

与studenList相同的输出,但我只为每个Map[String,Students]输出循环一次。

Map(Mumbai -> Students(Mumbai,,,,790,0), 
    Bangalore -> Students(Bangalore,,,,270,0))
Map((Mumbai,College1) -> Students(Mumbai,College1,,,550,0), 
    (Mumbai,College2) -> Students(Mumbai,College2,,,240,0), 
    (Bangalore,College3) -> Students(Bangalore,College3,,,270,0))
Map((Mumbai,College1,Science) -> Students(Mumbai,College1,Science,,300,0), 
    (Mumbai,College1,Social) -> Students(Mumbai,College1,Social,,250,0), 
    (Mumbai,College2,Science) -> Students(Mumbai,College2,Science,,240,0), 
    (Bangalore,College3,Science) -> Students(Bangalore,College3,Science,,270,0))

使用FoldLeft One Loop

您可以使用所有列表生成一个大地图。

val emptyStudent = Students("","","","",0,0);
studentsList.foldLeft(Map[(String,String,String), Students]()) { case (m, v) =>
  {
    var t = m + ((v.city,"","") -> Students(v.city,"","","",
      v.fee + m.getOrElse((v.city,"",""), emptyStudent).fee, 0))
    t = t + ((v.city,v.college,"") -> Students(v.city,v.college,"","",
      v.fee + m.getOrElse((v.city,v.college,""), emptyStudent).fee, 0))
    t + ((v.city,v.college,v.group) -> Students(v.city,v.college,v.group,"",
      v.fee + m.getOrElse((v.city,v.college,v.group), emptyStudent).fee, 0))
  }
}

<强>输出

在这种情况下,您循环一次并返回所有聚合的结果,但仅在一个Map中。这也适用于理解。

Map((Mumbai,College1,Science) -> Students(Mumbai,College1,Science,,300,0), 
    (Bangalore,,) -> Students(Bangalore,,,,270,0), 
    (Mumbai,College2,Science) -> Students(Mumbai,College2,Science,,240,0), 
    (Mumbai,College2,) -> Students(Mumbai,College2,,,240,0), 
    (Mumbai,College1,Social) -> Students(Mumbai,College1,Social,,250,0), 
    (Mumbai,,) -> Students(Mumbai,,,,790,0), 
    (Bangalore,College3,) -> Students(Bangalore,College3,,,270,0), 
    (Mumbai,College1,) -> Students(Mumbai,College1,,,550,0), 
    (Bangalore,College3,Science) -> Students(Bangalore,College3,Science,,270,0))

始终会复制地图,因此可能会出现一些性能和内存问题。要解决这个问题,请使用“理解”

对于理解一个循环

这将生成一个包含3种聚合类型的Map。

val emptyStudent = Students("","","","",0,0);
var m = Map[(String,String,String), Students]()
for (v <- studentsList) {
  m +=  ((v.city,"","") -> Students(v.city,"","","", v.fee + m.getOrElse((v.city,"",""), emptyStudent).fee, 0))
  m += ((v.city,v.college,"") -> Students(v.city,v.college,"","", v.fee + m.getOrElse((v.city,v.college,""), emptyStudent).fee, 0))
  m += ((v.city,v.college,v.group) -> Students(v.city,v.college,v.group,"", v.fee + m.getOrElse((v.city,v.college,v.group), emptyStudent).fee, 0))
}
m

这在内存消耗方面应该更好,因为你不像foldLeft示例中那样复制地图

<强>输出

Map((Mumbai,College1,Science) -> Students(Mumbai,College1,Science,,300,0), 
(Bangalore,,) -> Students(Bangalore,,,,270,0), 
(Mumbai,College2,Science) -> Students(Mumbai,College2,Science,,240,0), 
(Mumbai,College2,) -> Students(Mumbai,College2,,,240,0), 
(Mumbai,College1,Social) -> Students(Mumbai,College1,Social,,250,0), 
(Mumbai,,) -> Students(Mumbai,,,,790,0), (Bangalore,College3,) -> Students(Bangalore,College3,,,270,0), 
(Mumbai,College1,) -> Students(Mumbai,College1,,,550,0), 
(Bangalore,College3,Science) -> Students(Bangalore,College3,Science,,270,0))

在所有情况下,如果您在案例类学生中将参数设置为可选,则可以减少代码,因为您可以在分组期间执行类似Students(city=v.city,fee=v.fee+m.getOrElse(v.city,emptyStudent).fee的操作

答案 1 :(得分:1)

使用foldLeft

首先,让我们定义一些类型别名以使语法更容易

object GroupByStudents {

type City = String
type College = String
type Group = String
type Name = String

type Aggregate = Map[City, Map[College, Map[Group, List[Students]]]]
def emptyAggregate: Aggregate = Map.empty

case class Students(city: City, college: College, group: Group,
                  name: Name, fee: Int, age: Int)
}

您可以将学生列表汇总到一个Aggregate

中的foldLeft地图中
object Test {

import GroupByStudents._

def main(args: Array[String]) {
   val studentsList = List(
     Students("Mumbai","College1","Science","Jony",100,30),
     Students("Mumbai","College1","Science","Tony", 200, 25),
     Students("Mumbai","College1","Social","Bony",250,30),
     Students("Mumbai","College2","Science","Gony", 240, 28),
     Students("Bangalore","College3","Science","Hony", 270, 28))

   val aggregated = studentsList.foldLeft(emptyAggregate){(agg, students) =>
     val cityBin = agg.getOrElse(students.city, Map.empty)
     val collegeBin = cityBin.getOrElse(students.college, Map.empty)
     val groupBin = collegeBin.getOrElse(students.group, List.empty)

     val nextGroupBin = students :: groupBin
     val nextCollegeBin= collegeBin + (students.group -> nextGroupBin)
     val nextCityBin = cityBin + (students.college -> nextCollegeBin)
     agg + (students.city -> nextCityBin)
     }
   }
}
然后可以映射

aggregated来计算费用。 如果你真的想要,你可以在foldLeft本身计算费用,但这会使代码更难阅读。

请注意,您也可以尝试monocle's lenses将学生的价值放在汇总结构中。