我有以下代码:
Sighting.all
.iterator
.map(s => (s, haversineDistance(s, ourLocation)))
.toSeq
.sortBy(_._2)
.take(5)
正如预期的那样,它会将近5次目击返回ourLocation
。
但是,对于大量的目击事件,它不能很好地扩展。我们可以通过所有目击O(N)找到最近的5个,而不是将它们全部排序,从而做O(N * logN)。如何惯用这个?
答案 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元素列表,如下所示:
完成迭代后,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] = {
...
}
}