我有一个我想要排序的对象数组,其中排序的谓词是异步的。 Scala是否具有基于类型签名为(T, T) -> Future[Bool]
而不仅仅是(T, T) -> Bool
的谓词进行排序的标准或第三方库函数?
或者,是否有其他方法可以构建此代码?我已经考虑过查找列表元素的所有2对排列,在每对上运行谓词并将结果存储在Map((T, T), Bool)
或某种结构中,然后对其进行排序 - 但我怀疑它会比起一个天真的排序算法还要执行更多的比较。
答案 0 :(得分:1)
如果您的谓词是异步的,您可能也希望获得异步结果并避免使用Await
阻止线程
如果要根据未来的布尔谓词对List[(T,T)]
进行排序,最简单的方法是对List[(T,T,Boolean)]
进行排序
因此,如果您有List[(T,T)]
和谓词(T, T) -> Future[Bool]
,那么如何获得List[(T,T,Boolean)]
?或者更确切地说是Future[List[(T,T,Boolean)]]
,因为您希望保持异步行为。
val list: List[(T,T)] = ...
val predicate = ...
val listOfFutures: List[Future[(T,T,Boolean]] = list.map { tuple2 =>
predicate(tuple2).map( bool => (tuple2._1, tuple2._2, bool)
}
val futureList: Future[List[(T,T,Boolean)]] = Future.sequence(listOfFutures)
val futureSortedResult: Future[List[(T,T)]] = futureList.map { list =>
list.sort(_._3).map(tuple3 => (tuple3._1,tuple3._2))
}
这是伪代码,我没有编译它可能没有,但你明白了。
密钥是Future.sequence
,非常有用,它以某种方式允许将Monad1[Monad2[X]]
转换为Monad2[Monad1[X]]
,但请注意,如果任何谓词未来失败,则全局排序操作也将是故障。
如果您希望获得更好的性能,那么“批量”调用返回Future[Boolean]
的服务可能是更好的解决方案。
例如,您可以设计一个服务(如果您显然拥有它),而不是(T, T) -> Future[Bool]
,例如List[(T, T)] -> Future[List[(T,T,Bool)]
,这样您就可以在异步单个调用中获得所需的一切。
答案 1 :(得分:0)
不太令人满意的替代方案是阻止每次比较,直到评估未来。如果评估排序谓词很昂贵,排序将花费很长时间。实际上,这只是将可能并发的程序转换为顺序程序;使用期货的所有好处都将丢失。
import scala.concurrent.duration._
implicit val executionContext = ExecutionContext.Implicits.global
val sortingPredicate: (Int, Int) => Future[Boolean] = (a, b) => Future{
Thread.sleep(20) // Assume this is a costly comparison
a < b
}
val unsorted = List(4, 2, 1, 5, 7, 3, 6, 8, 3, 12, 1, 3, 2, 1)
val sorted = unsorted.sortWith((a, b) =>
Await.result(sortingPredicate(a, b), 5000.millis) // careful: May throw an exception
)
println(sorted) // List(1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 12)
我不知道是否有利用异步比较的开箱即用解决方案。但是,您可以尝试实现自己的排序算法。如果我们考虑平均在O(n log(n))
中运行的Quicksort,那么我们实际上可以非常容易地利用异步比较。
如果您不熟悉Quicksort,该算法基本上会执行以下操作
由于步骤2执行了大量独立比较,我们可以同时评估比较。
这是一个未经优化的实施:
object ParallelSort {
val timeout = Duration.Inf
implicit class QuickSort[U](elements: Seq[U]) {
private def choosePivot: (U, Seq[U]) = elements.head -> elements.tail
def sortParallelWith(predicate: (U, U) => Future[Boolean]): Seq[U] =
if (elements.isEmpty || elements.size == 1) elements
else if (elements.size == 2) {
if (Await.result(predicate(elements.head, elements.tail.head), timeout)) elements else elements.reverse
}
else {
val (pivot, other) = choosePivot
val ordering: Seq[(Future[Boolean], U)] = other map { element => predicate(element, pivot) -> element }
// This is where we utilize asynchronous evaluation of the sorting predicate
val (left, right) = ordering.partition { case (lessThanPivot, _) => Await.result(lessThanPivot, timeout) }
val leftSorted = left.map(_._2).sortParallelWith(predicate)
val rightSorted = right.map(_._2).sortParallelWith(predicate)
leftSorted ++ (pivot +: rightSorted)
}
}
}
可以使用(与上面相同的例子)如下:
import ParallelSort.QuickSort
val sorted2 = unsorted.sortParallelWith(sortingPredicate)
println(sorted2) // List(1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 12)
请注意,Quicksort的这种实现是否比完全顺序的内置排序算法更快或更慢,这在很大程度上取决于比较的成本:比较阻止的时间越长,上面提到的替代解决方案就越糟糕。在我的机器上,给出了昂贵的比较(20毫秒)和上面的列表,内置排序算法运行在~1200毫秒,而这个自定义Quicksort运行在~200毫秒。如果您担心性能问题,那么您可能想要提出更聪明的方法。 编辑:我刚刚检查了内置排序算法和自定义Quicksort算法执行的比较:显然,对于给定列表(以及我随机输入的其他一些列表)内置算法使用了更多的比较,因此并行执行可能不会带来性能提升。我不了解更大的列表,但您无论如何都必须根据您的特定数据对其进行分析。