在 Scala 中提取域后缀并计算命中计数



寻找一些建议,以优化原版Scala中的解决方案,以解决我在教科书中遇到的问题:

假设您得到了一些这样的数据:


val counts = Array(
"900,google.com", 
"60,mail.yahoo.com", 
"40,sports.yahoo.com", 
"50,mobile.sports.yahoo.com", 
"3,en.wikipedia.org"
)

第一个值表示域收到的命中数。我想做的是能够汇总每个域和属于它的每个子域的计数。所以输出应该看起来像这样:

res = List(
("com", 1050),
("google.com", 900),
("yahoo.com", 150),
("sports.yahoo.com", 90),
("mail.yahoo.com", 60),
("mobile.sports.yahoo.com", 50),
("sports.yahoo.com", 40),
("org", 3),
("en.wikipedia.org",3)
)

下面的代码至少在提供的数据上有效:

object Solution {
val counts = Array(
"900,google.com",
"60,mail.yahoo.com",
"10,mobile.sports.yahoo.com",
"40,sports.yahoo.com",
"10,stackoverflow.com",
"2,en.wikipedia.org",
"1,es.wikipedia.org",
"1,mobile.sports"
)
case class DomainMapEntry(count: Int, suffix: String)

private def extractSuffixes(str: String): List[String] =
str.split('.').foldLeft(List.empty[String]) { (acc, part) =>
part :: acc.map(rest => s"$rest.$part")
}
// split each entry in the array, extract suffixes and pair with count.
private def createDomainMap(str: String): List[Option[DomainMapEntry]] =
str.split(",").take(2).toList match {
case h :: t :: Nil => extractSuffixes(t).map(suffix => Some(DomainMapEntry(Integer.parseInt(h), suffix)))
case _             => List(None)
}
// create a map of suffixes and counts, flatten and group by suffixes,
// sum the grouped counts, and sort in reverse
def parseCounts(arr: Array[String]) =
arr
.map(createDomainMap)
.flatten
.flatten
.groupBy(_.suffix)
.mapValues(_.map(_.count).sum)
.toList
.sortBy(-_._2)
def main(args: Array[String]): Unit = println(parseCounts(counts).mkString("n"))
}

但是,我觉得它可以显着改进。显然,case class可能只是一个元组,但从算法上讲,我觉得我错过了一些东西,我也不太担心格式不正确的数据,只是一个练习。

  1. 我可以更优雅地(简单地说,性能更好(提取带有计数的后缀吗?

  2. 那个双重flatten调用,也许是代码气味?

  3. 拆分并重新连接以创建后缀模式似乎很卡顿?

有许多不同的方法可以处理这样的任务。这里有一个替代方案。

counts.flatMap{ str =>
val Array(num,domain) = str.split(",")
domain.split("\.")
.tails
.collect{case d if d.nonEmpty => (d.mkString("."), num.toInt)}
}.groupMapReduce(_._1)(_._2)(_+_)  //Scala 2.13.0
.toList
.sortBy(-_._2)
//res0: List[(String, Int)] = List((com,1050)
//                               , (google.com,900)
//                               , (yahoo.com,150)
//                               , (sports.yahoo.com,90)
//                               , (mail.yahoo.com,60)
//                               , (mobile.sports.yahoo.com,50)
//                               , (wikipedia.org,3)
//                               , (org,3)
//                               , (en.wikipedia.org,3))

请注意,这不会筛选错误的输入,因此必须事先添加或完成。

最新更新