2015-05-29 13 views
5

Mam tablicę obiektów, które chcę sortować, gdzie predykat sortowania jest asynchroniczny. Czy Scala ma funkcję sortowania standardową lub trzecią stroną do sortowania na podstawie predykatu o sygnaturze typu (T, T) -> Future[Bool], a nie tylko (T, T) -> Bool?Scala - sortowanie na podstawie predykatu wyników Future

Czy jest jakiś inny sposób, w jaki mogę skonstruować ten kod? Zastanawiałem się nad znalezieniem wszystkich 2-parowych permutacji elementów listy, uruchomieniem predykatu nad każdą parą i przechowywaniem wyniku w postaci pewnej struktury, a następnie sortowania na nim - ale podejrzewam, że będzie miało o wiele więcej porównań wykonane, niż mógłby to zrobić nawet algorytm naiwnego sortowania.

+1

Wstępne obliczanie kolejności wszystkich par wymaga porównań 'n^2', podczas gdy większość algorytmów sortowania działa w' n log (n) ', więc nie chciałbyś tego robić. –

+0

To było moje podejrzenie! Zdecydowanie zły pomysł (TM). – jackweirdy

Odpowiedz

1

Jeśli orzecznik jest asynchroniczny może wolisz, aby uzyskać wynik asynchronicznej zbyt i uniknąć blokowania wątki używając Await

Jeśli chcesz posortować List[(T,T)] według przyszłego logiczną orzecznika, najprostszy to posortować List[(T,T,Boolean)]

W związku z tym, czy masz List[(T,T)] i predykat (T, T) -> Future[Bool], jak uzyskać List[(T,T,Boolean)]? Lub raczej Future[List[(T,T,Boolean)]], jeśli chcesz zachować zachowanie asynchroniczne.

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)) 
} 

To jest pseudo-kod, nie skompilowałem go i może nie, ale masz pomysł.

Kluczem jest Future.sequence, bardzo użyteczny, który w jakiś sposób pozwala przekształcić Monad1[Monad2[X]] do Monad2[Monad1[X]] jednak zauważyć, że jeśli któryś z twojej kwantyfikatorów przyszłości uda, globalna operacja sortowania będzie również porażką.


Jeśli chcesz lepszą wydajność może być lepszym rozwiązaniem „partia” wezwanie do służby zwracajaca Future[Boolean]. Na przykład zamiast (T, T) -> Future[Bool] możesz zaprojektować usługę (jeśli oczywiście ją posiadasz), taką jak List[(T, T)] -> Future[List[(T,T,Bool)], dzięki czemu możesz uzyskać wszystko, czego potrzebujesz w asynchronicznej pojedynczej rozmowie.

0

Niezadowalającą alternatywą byłoby zablokowanie każdego porównania do momentu oceny przyszłości. Jeśli ocena predykatu sortowania jest kosztowna, sortowanie zajmie dużo czasu. W rzeczywistości przekłada to prawdopodobnie program współbieżny na sekwencyjny; wszystkie korzyści z używania kontraktów terminowych zostaną utracone.

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) 

Nie wiem, czy jest się z roztworu, który wykorzystuje pole asynchroniczny porównanie. Możesz jednak spróbować zaimplementować własny algorytm sortowania. Jeśli weźmiemy pod uwagę Quicksort, który działa przeciętnie w O(n log(n)), możemy faktycznie wykorzystać porównanie asynchroniczne dość łatwo.

Jeśli nie jesteś zaznajomiony z Quicksort algorytm zasadzie wykonuje następujące

  1. Wybierz element z kolekcji (zwanej Pivot)
  2. Porównaj pivot ze wszystkimi pozostałymi elementami. Utwórz kolekcję z elementami mniejszymi niż oś przestawna i jedną z elementami, które są większe niż oś przestawna.
  3. Porządkuj dwie nowe kolekcje i połącz je, umieszczając oś obrotu pośrodku.

Ponieważ w kroku 2 wykonano wiele niezależnych porównań, możemy jednocześnie dokonać porównania porównań.

Oto unoptimized realizacja:

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) 
     } 
    } 

} 

które mogą być wykorzystane (sam przykład jak powyżej) w następujący sposób:

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) 

Zauważ, że czy to wdrożenie QuickSort jest szybciej lub wolniej niż Całkowicie sekwencyjny wbudowany algorytm sortowania bardzo zależy od kosztu porównania: im dłużej porównanie musi blokować, tym gorsze jest wspomniane wyżej rozwiązanie alternatywne. Na moim komputerze, biorąc pod uwagę kosztowne porównanie (20 milisekund) i powyższą listę, wbudowany algorytm sortowania działa w ~ 1200 ms, podczas gdy ten niestandardowy Quicksort działa w ~ 200 ms. Jeśli martwisz się o wydajność, prawdopodobnie chcesz wymyślić coś mądrzejszego. Edycja: Właśnie sprawdziłem, ile porównań zarówno wbudowany algorytm sortowania, jak i niestandardowy algorytm Quicksort wykonują: Podobno dla danej listy (i kilku innych list wpisywanych losowo) wbudowany algorytm wykorzystuje więcej porównań, więc poprawa wydajności dzięki równoległemu wykonaniu może nie być tak wielka. Nie wiem o większych listach, ale i tak musisz je profilować na swoich konkretnych danych.