1

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

例子:

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)))

在这两种情况下,循环列表不止一次。有什么方法可以通过单通道和优化方式来实现这一点。

4

2 回答 2

4

使用 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

输出

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

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

带左折叠

只需遍历完整列表即可。

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))
}

输出

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

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 一个循环

您可以只生成一张包含所有列表的大地图。

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))
  }
}

输出

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

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))

地图总是被复制的,所以它可能有一些性能和内存问题。为了解决这个问题,请使用 for comprehension

领悟一环

这会生成一个具有 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在分组期间执行类似的操作

于 2016-03-09T20:11:07.607 回答
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自己计算费用,但这会使代码更难阅读。

请注意,您也可以尝试使用单片眼镜的镜片,将学生的价值放入聚合结构中。

于 2016-03-09T21:02:19.667 回答