2016-03-09 9 views
0

У меня есть список классов case. Вывод требует агрегации по различным параметрам класса case. Ищите более оптимизированный способ сделать это.Лучший способ для агрегации по списку классов case

Пример:

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

Теперь, чтобы получить сведения студентов из одного города, мне нужно первого агрегата по городу, а затем ломка те детали колледжа мудр, то мудр группу.

Выход - это список классов case в нижнем формате.

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

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

Выход

В этом случае цикл один раз и получить обратно результаты для всех агрегацию, но только в 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)) 

Карта всегда копируется, поэтому у нее могут быть проблемы с производительностью и памятью. Для решения этого используйте для понимания

Для постижение One Loop

Это создает одну карту с 3-х агрегатных типов.

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

Во всех случаях можно просто уменьшить код, если вы сделаете параметр необязательным в ваших учениках класса case, вы можете сделать что-то вроде Students(city=v.city,fee=v.fee+m.getOrElse(v.city,emptyStudent).fee во время группировки

+0

Извините. На самом деле я ищу упомянутый вывод, и агрегация происходит и по некоторым другим параметрам. Невозможно найти чистый способ генерации указанного вывода. – jony

+0

@jony ok подумал, что плата достаточно, потому что другой параметр очевиден. – Kordi

+0

@jony теперь у меня есть точный вывод, а код немного красивее, но я не думаю, что он быстрее, у меня есть только первая группа примеров по городу, а другая прямолинейная, потому что группировка такая же, как у вас Пример – Kordi

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, чтобы поставить учащихся в агрегированную структуру.

+0

Спасибо за аккуратный ответ. Как уже упоминалось, для вывода требуется еще один проход через все значения карты. – jony