如何有效地从我的收藏中获取n个最常见的元素?

时间:2017-10-01 20:34:28

标签: scala scala-collections

我正在尝试做这样的事情:

List("c","a","b","b","a","a").groupBy(identity).toList.sortBy(_._2.size).reverse.take(2).map(_._1)

这会产生我需要的结果,两个最常出现的列表:

 List("a", "b")

只有这样效率低下,因为groupBy创建了一堆我不需要的集合;一个简单的计数就足够了。

这样做的惯用方法是什么?

想象一下,原始列表很长,而不同值的数量很小。

2 个答案:

答案 0 :(得分:0)

最简单的解决方案是迭代列表并计算字符串的频率。

使用功能方法实现(使用foldLeft over collection):

 object Main extends App {

   val strings: List[String] = List("c", "a", "b", "b", "a", "a")

   val counts: Map[String, Int] = strings.foldLeft(Map.empty[String, Int]) { (map, string) =>
     val count: Int = map.getOrElse(string, 0)  //get the current count of the string
     map.updated(string, count + 1)  //update the map by incrementing string's counter
   }

   println(s"counts = $counts")

 }

通过此实现,您既可以避免中间集合,又可以实现O(n)时间。

从这一点开始,您可以使用counts来实现目标。如果你想获得最常用的单词,例如:

 val sortedFrequency: Vector[(String, Int)] = counts.toVector.sortWith(_._2 > _._2)
 println(s"sorted frequency${sortedFrequency}")

由于我们假设生成的counts映射的大小很小,因此可以忽略对该集合进行排序的操作。

答案 1 :(得分:0)

正如我在对其他答案的评论中提到的,foldLeft解决方案实际上不如原始.groupBy

请注意,您似乎在此处优化错误的事情:计算重复项是O(N)两种方式,而排序它们以找到最常见的是O(N*log(N)),所以很可能成为实施绩效的主导因素。

您可以避免必须对计数进行排序,只需保留一个小数组或目前为止最大的两个元组,并在进行单线性扫描时更新它。

grouped
  .mapValues(_.size)
  .foldLeft(("", Int.MinValue) -> ("", Int.MinValue)) {
     case (((a, x), (b,y)), (_, cnt)) if cnt < x => (a,x) -> (b,y)
     case ((_, (b,y)), (str, cnt)) if cnt < y => (str, cnt) -> (b, y)
     case ((_, keep), (str, cnt)) => keep -> (str, cnt)
  }   

使用有界PriorityQueuehttps://gist.github.com/ryanlecompte/5746241

,这是同一个想法的更精彩的实现