根据某些标准采用前n个值的惯用方法是什么?

时间:2017-10-01 23:40:21

标签: scala scala-collections

我有以下代码:

Sighting.all
      .iterator
      .map(s => (s, haversineDistance(s, ourLocation)))
      .toSeq
      .sortBy(_._2)
      .take(5)

正如预期的那样,它会将近5次目击返回ourLocation

但是,对于大量的目击事件,它不能很好地扩展。我们可以通过所有目击O(N)找到最近的5个,而不是将它们全部排序,从而做O(N * logN)。如何惯用这个?

3 个答案:

答案 0 :(得分:2)

与之前的问题一样,fold可能会有用。在这种情况下,我很想将PriorityQueue初始化为大于预期数据集的值。

import scala.collection.mutable.PriorityQueue

...
.iterator
.foldLeft(PriorityQueue((999,"x"),(999,"x"),(999,"x"),(999,"x"),(999,"x")){
  case (pq, s) => pq.+=((haversineDistance(s, ourLocation), s)).tail
}

结果是PriorityQueue为5(距离,目击)元组,但只有5个最小距离。

答案 1 :(得分:2)

您可以通过迭代列表中的每个元素一次来避免对大列表进行排序,同时保持5元素列表,如下所示:

  1. 保持5个元素的列表按距离按降序排序,使其头部元素的距离最长(注意,因为5很小,所以排序成本可以忽略不计)
  2. 在每次迭代中,如果原始列表中的当前元素的距离比5元素列表中的head元素的距离短,则将head元素替换为当前元素;否则保持当前的5元素列表
  3. 完成迭代后,5元素列表将由具有最短距离的元素组成,按距离按升序排序的最终排序将给出前5个列表:

    val list = Sighting.all.
      iterator.
      map(s => (s, haversineDistance(s, ourLocation))).
      toSeq
    
    // For example ...
    res1: list = List(
      ("a", 5), ("b", 2), ("c", 12), ("d", 9), ("e", 6), ("f", 15),
      ("g", 9), ("h", 7), ("i", 6), ("j", 3), ("k", 10), ("l", 5)
    )
    
    val top5 = list.drop(5).
      foldLeft( list.take(5).sortWith(_._2 > _._2) )(
        (l, e) => if (e._2 < l.head._2)
                    (e :: l.tail).sortWith(_._2 > _._2)
                  else
                    l
      ).
      sortBy(_._2)
    // top5: List[(String, Int)] = List((b,2), (f,3), (h,5), (a,5), (e,6))
    

    [UPDATE]

    以下是上述top5值赋值的详细版本,有望使foldLeft表达式看起来不那么强大。

    val initialTop5Sorted = list.take(5).sortWith(_._2 > _._2)
    
    val originalListTail = list.drop(5)
    
    def updateTop5Sorted = ( list: List[(String, Int)], element: (String, Int) ) => {
      if (element._2 < list.head._2)
        (element :: list.tail).sortWith(_._2 > _._2)
      else
        list
    }
    
    val top5 = originalListTail.
      foldLeft( initialTop5Sorted )( updateTop5Sorted ).
      sortBy(_._2)
    

    此处有foldLeft的签名供您参考:

    def foldLeft[B](z: B)(op: (B, A) => B): B
    

答案 2 :(得分:1)

这是一种略有不同的方法:

const color = d.y >= 70 ? "green" : d.y >= 50 ? "yellow" : "red";

def topNBy[A, B : Ordering](xs: Iterable[A], n: Int, f: A => B): List[A] = { val q = new scala.collection.mutable.PriorityQueue[A]()(Ordering.by(f)) for (x <- xs) { q += x if (q.size > n) { q.dequeue() } } q.dequeueAll.toList.reverse } 很有用,值得熟悉,但是如果你不是在每次迭代中创建一个新对象来进行操作,而只是修改现有的对象,那么它就不比for循环好。我宁愿依靠fold进行排序而不是自己进行排序,特别是考虑到它是一个有效的O(log n)实现。功能纯粹主义者可能会因为更加迫切而不愿意这样做,但对我而言,它的可读性和简洁性是值得的。可变状态仅限于单个本地数据结构。

你甚至可以把它放在一个隐含的类中:

PriorityQueue

然后使用它:

implicit class IterableWithTopN[A](xs: Iterable[A]) {
  def topNBy[B : Ordering](n: Int, f: A => B): List[A] = {
    ...
  }
}