Вложенная группаBy & Aggregation в Scala - PullRequest
0 голосов
/ 11 октября 2018

Я пытаюсь groupBy на основе ResourceId & Category и вернуть соответствующий наивысший доступный уровень серьезности.Иерархия серьезности - Критическая> Основная> Малая.т.е. после группировки по ResourceId & Category нам нужно вернуть наивысшую серьезность для группы.

case class Issue(
  resourceId: String, 
  Category: String, 
  Severity: String, 
  incidentType: String
)

case class IssueStatus(
  resourceId:String, 
  Hardware: Option[String],
  Network: Option[String], 
  Software: Option[String]
)

List(
  Issue("r1", "Network", "Critical", "incident1"),
  Issue("r1", "Network", "Major", "incident2"),
  Issue("r1", "Hardware", "Minor", "incident 3"),
  Issue("r2", "Hardware", "Major", "incident 3"),
  Issue("r3", "Software", "Minor", "incident 1"),
)

Ожидаемый результат :

List(
  IssueStatus("r1", Some("Minor"), Some("Critical"), None),
  IssueStatus("r2", Some("Major"), None, None),
  IssueStatus("r3", None, None, Some("Minor"))
)

Обновление:

Категория сопоставлена ​​с объектом дела.т.е. у нас есть только 3 категории: сеть, оборудование и программное обеспечение.

Для каждого ресурса я хочу знать, какой уровень серьезности в каждой категории.Если категория сети имеет наивысшую степень серьезности как критическую и для категорий программного и аппаратного обеспечения для ресурса r5 нет записей, то соответствующий IssueStatus будет выглядеть как

IssueStatus("r5", None, Some("Critical"), None)

Ответы [ 5 ]

0 голосов
/ 11 октября 2018

Еще один вариант решения:)

val input = List(
  Issue("r1", "Network", "Critical", "incident1"),
  Issue("r1", "Network", "Major", "incident2"),
  Issue("r1", "Hardware", "Major", "incident5"),
  Issue("r1", "Hardware", "Minor", "incident 3"),
  Issue("r2", "Hardware", "Major", "incident 6"),
  Issue("r2", "Hardware", "Critical", "incident 13"),
  Issue("r3", "Software", "Minor", "incident 1"),
  Issue("r3", "Network", "Major", "incident 1"),
)


val ranked = input.groupBy(_.resourceId).flatMap {case (resourceId, issuesByResource) =>
    issuesByResource.groupBy(_.Category). map { case (category, issuesByCategoryPerResource) =>
      implicit val _ : Ordering[Issue] = (lhs: Issue, rhs: Issue) => {
        (lhs.Severity, rhs.Severity) match {
          case ("Critical", _) => -1
          case (_, "Critical") => 1
          case ("Major", _) => -1
          case (_, "Major") => 1
          case _ => -1
        }
      }
      (resourceId, category, issuesByCategoryPerResource.min.Severity)
    }
}


val grouped = ranked.groupBy(_._1)
val resourceIdToRawIssueStatus = grouped.mapValues { _. map {case (_, cat, sev) => cat -> sev}.toMap}

resourceIdToRawIssueStatus.map{ case (rId, statusesByCat) =>
    IssueStatus(rId, statusesByCat.get("Hardware"), statusesByCat.get("Network"), statusesByCat.get("Software"))
}

Небольшая заметка, я обычно не люблю использовать mapValues, так как на самом деле это "просмотр"

0 голосов
/ 11 октября 2018

Я близок к последнему шагу.Все еще работает над объединением IssueStatus на основе resourceId.Проверьте это.

scala> case class Issue(
     |   resourceId: String,
     |   Category: String,
     |   Severity: String,
     |   incidentType: String
     | )
defined class Issue

scala> case class IssueStatus(
     |   resourceId:String,
     |   Hardware: Option[String],
     |   Network: Option[String],
     |   Software: Option[String]
     | )
defined class IssueStatus

scala>

scala> val issueList = List(
     |   Issue("r1", "Network", "Critical", "incident1"),
     |   Issue("r1", "Network", "Major", "incident2"),
     |   Issue("r1", "Hardware", "Minor", "incident 3"),
     |   Issue("r2", "Hardware", "Major", "incident 3"),
     |   Issue("r3", "Software", "Minor", "incident 1")
     | )
issueList: List[Issue] = List(Issue(r1,Network,Critical,incident1), Issue(r1,Network,Major,incident2), Issue(r1,Hardware,Minor,incident 3), Issue(r2,Hardware,Major,incident 3), Issue(r3,Software,Minor,incident 1))

scala> val proc1 = issueList.groupBy( x=> (x.resourceId,x.Category)).map( x=>(x._1,(x._2).sortWith( (p,q) => p.Category > q.Category)(0))).map( x=> (x._1._1,x._1._2,x._2.Severity))
proc1: scala.collection.immutable.Iterable[(String, String, String)] = List((r1,Hardware,Minor), (r3,Software,Minor), (r2,Hardware,Major), (r1,Network,Critical))

scala> val proc2 = proc1.map( x => x match { case(a,"Hardware",c) => IssueStatus(a,Some(c),None,None) case(a,"Network",c) => IssueStatus(a,None,Some(c),None) case(a,"Software",c) => IssueStatus(a,None,None,Some(c)) } )
proc2: scala.collection.immutable.Iterable[IssueStatus] = List(IssueStatus(r1,Some(Minor),None,None), IssueStatus(r3,None,None,Some(Minor)), IssueStatus(r2,Some(Major),None,None), IssueStatus(r1,None,Some(Critical),None))

scala>

scala> proc2.foreach(println)
IssueStatus(r1,Some(Minor),None,None)
IssueStatus(r3,None,None,Some(Minor))
IssueStatus(r2,Some(Major),None,None)
IssueStatus(r1,None,Some(Critical),None)

scala>
0 голосов
/ 11 октября 2018

Вот мой взгляд на «проблему».

val input = List(
  Issue("r1", "Network", "Critical", "incident1"),
  Issue("r1", "Network", "Major", "incident2"),
  Issue("r1", "Hardware", "Minor", "incident 3"),
  Issue("r2", "Hardware", "Major", "incident 3"),
  Issue("r3", "Software", "Minor", "incident 1"),
  Issue("r3", "Software", "Critical", "incident 1"), // added 2 more for testing
  Issue("r3", "Software", "Major", "incident 1"),
)

val res = input.groupBy(_.resourceId)
  .mapValues(_.groupBy(_.Category)
    .mapValues(_.map(_.Severity).min))
  .map{ case (k,m) => 
    IssueStatus(k, m.get("Hardware"), m.get("Network"), m.get("Software"))
  }.toList

//res: List[IssueStatus] = List(IssueStatus(r3,None,None,Some(Critical))
//                            , IssueStatus(r2,Some(Major),None,None)
//                            , IssueStatus(r1,Some(Minor),Some(Critical),None))

Примечание: есть неудачный маленький взлом, поскольку он основан на алфавитном порядке «Критический», «Главный» и «Незначительный»,с более ранним приоритетом над последним.Это не сработало бы, если бы строки Severity были "Плохо", "Очень плохо" и "Обречены".

0 голосов
/ 11 октября 2018

Я считаю, что это то, что вы ищете:

def highestIssueStatus(issues: List[Issue]): IssueStatus = {
  def issueRank(issue: Issue): Int =
    List("Minor", "Major", "Critical").indexOf(issue.Severity)

  val high = issues
      .groupBy(_.Category)
      .mapValues(_.maxBy(issueRank).Severity)

    IssueStatus(
      issues.head.resourceId,
      high.get("Hardware"),
      high.get("Network"),
      high.get("Software")
    )
}

list.groupBy(_.resourceId).values.map(highestIssueStatus)

Обновление

Спасибо Yaneeve за указание на ошибку в оригинале (issueRank искал _.Categoryвместо _.Severity)

Оптимизация

После комментария от OP, здесь есть более оптимизированное и менее функциональное решение этой проблемы.Он строит ответы в изменчивую карту за один проход, а не с использованием groupBy, а затем обрабатывает результаты.

val categories = Vector("Hardware", "Network", "Software")
val severities = Vector("Minor", "Major", "Critical")
val results = Vector(None) ++ severities.map(Some(_))

def parseIssues(issues: List[Issue]) = {
  val issueMap = mutable.Map.empty[String, ArrayBuffer[Int]]

  issues.foreach{ issue =>
    val cat = categories.indexOf(issue.Category) + 1
    val sev = severities.indexOf(issue.Severity) + 1
    val cur = issueMap.get(issue.resourceId) match {
      case Some(v) => v
      case None =>
        val n = ArrayBuffer(0, 0, 0, 0)
        issueMap(issue.resourceId) = n
        n
    }

    if (cur(cat) < sev) {
      cur(cat) = sev
    }
  }

  issueMap.map{ case (k, v) =>
    IssueStatus(k, results(v(1)), results(v(2)), results(v(3)))
  }
}

Другая оптимизация заключалась бы в использовании скалярных значений вместо String для категорий иВажности.Это позволило бы избежать необходимости вызовов indexOf в основном цикле и позволило бы mutable.Map хранить Option[Severity] напрямую, а не как индекс для results.

. Этот подход также можно использовать впотоковый режим, в котором обновления статуса постоянно добавляются в Map по мере их поступления, и последний статус может быть извлечен в любой момент.Значения карты являются изменяемыми, поэтому состояние ресурса можно сбросить до 0 (None) после устранения проблемы.Здесь необходимо рассмотреть вопросы безопасности потоков, чтобы их можно было, например, поместить в Akka Actor.

0 голосов
/ 11 октября 2018
case class Issue(
                 resourceId: String,
                 Category: String,
                 Severity: String,
                 incidentType: String
               )

case class IssueStatus(
                       resourceId: String,
                       Hardware: Option[String],
                       Network: Option[String],
                       Software: Option[String]
                      )

val p = List(
    Issue("r1", "Network", "Critical", "incident1"),
    Issue("r1", "Hardware", "Minor", "incident 3"),
    Issue("r2", "Hardware", "Major", "incident 3"),
    Issue("r3", "Software", "Minor", "incident 1")
  )

def getIssues(lstOfIssue: List[Issue], typeOfIssue: String): Option[String] = {
    lstOfIssue.find(_.Category == typeOfIssue) match {
      case Some(v) => Some(v.Severity)
      case _ => None
    }
  }

def computeIssueStatus(listOfIssues: List[Issue]): List[IssueStatus] = {
    listOfIssues.groupBy(issue => issue.resourceId)
      .map(kv =>
        IssueStatus(kv._1, getIssues(kv._2, "Hardware"), getIssues(kv._2, "Network"), getIssues(kv._2, "Software")))
      .toList
  }
computeIssueStatus(p)
...