Scala中的嵌套GroupBy和聚合

时间:2018-10-11 00:58:13

标签: scala collections scala-collections

我正在尝试根据groupByResourceIdCategory,并返回相应的最高严重性级别。 严重性等级为“严重”>“主要”>“次要”。即按ResourceIdCategory分组后,我们需要返回该组的最高严重性。

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 :(得分:2)

这是我对“问题”的看法。

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字符串为“不良”,“非常不良”和“变暗”,则此方法无效。

答案 1 :(得分:2)

我相信这可以满足您的需求:

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时不断添加,并且随时可以提取最新状态。映射值是可变的,因此当问题解决后,资源的状态可以重置为0None)。这里需要考虑线程安全问题,因此可以将其放置在Akka Actor中。

答案 2 :(得分:1)

再采取一种解决方案:)

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,因为它实际上是一个“视图”

答案 3 :(得分:0)

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)

答案 4 :(得分:-1)

我已经接近最后一步了。仍在基于resourceId合并IssueStatus的工作。检查一下。

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>