2012-06-06 10 views
41

Robię trochę gimnastyki Scala, gdzie mam Seq[T], w której staram się znaleźć element "najmniejszy". To co mam teraz zrobić:scala - Min/max z Opcją [T] dla ewentualnie pustego Seq?

val leastOrNone = seq.reduceOption { (best, current) => 
    if (current.something < best.something) current 
    else best 
} 

To działa dobrze, ale nie jestem całkiem zadowolony - to trochę długo taka prosta sprawa, a I don't care much for "if"s. Korzystanie minBy byłby znacznie bardziej eleganckie:

val least = seq.minBy(_.something) 

... ale min i minBy rzucać wyjątki, gdy sekwencja jest pusta. Czy istnieje idiomatyczny, bardziej elegancki sposób na znalezienie najmniejszego elementu możliwej pustej listy jako Option?

Odpowiedz

52
seq.reduceOption(_ min _) 

robi to, co chcesz?


Edycja: Oto przykład Zawierające _.something:

case class Foo(a: Int, b: Int) 
val seq = Seq(Foo(1,1),Foo(2,0),Foo(0,3)) 
val ord = Ordering.by((_: Foo).b) 
seq.reduceOption(ord.min) //Option[Foo] = Some(Foo(2,0)) 

lub jako metoda rodzajowa:

def minOptionBy[A, B: Ordering](seq: Seq[A])(f: A => B) = 
    seq reduceOption Ordering.by(f).min 

które można wywołać z minOptionBy(seq)(_.something)

+2

możliwe jest również użycie 'seq reduceOption math.min'. Jest to bardziej wydajne, ponieważ nie wymaga niejawnej konwersji. – sschaef

+0

@Antoras dobra myśl, ale uważam, że wszelkie różnice są w praktyce zoptymalizowane - przynajmniej to pokazuje mój mikrobenkmark –

-3

W Haskell chcesz zawinąć wywołanie minimumBy jako

least f x | Seq.null x = Nothing 
      | otherwise = Just (Seq.minimumBy f x) 
+0

Czy istnieje sposób _catch_ błąd rzucony przez 'minimumBy'? – missingfaktor

+0

Chciałbym uniknąć wyjątku w pierwszej kolejności - użyj strażnika, aby wykluczyć możliwość wystąpienia wyjątku. Jest to semantycznie czystsze niż opieranie się na semantyce wyjątku, aby dać Ci opcjonalną wartość. –

+3

Haskell jest fajny, bez wątpienia. Ale "tak można to zrobić w innym języku programowania" nie odpowiada na pytanie. – Utgarda

1

Jak na ten temat?

import util.control.Exception._ 
allCatch opt seq.minBy(_.something) 

Albo, bardziej gadatliwy, jeśli nie chcą połknąć inne wyjątki:

catching(classOf[UnsupportedOperationException]) opt seq.minBy(_.something) 

Alternatywnie, można pimp wszystkie zbiory z mniej więcej tak:

import collection._ 

class TraversableOnceExt[CC, A](coll: CC, asTraversable: CC => TraversableOnce[A]) { 

    def minOption(implicit cmp: Ordering[A]): Option[A] = { 
    val trav = asTraversable(coll) 
    if (trav.isEmpty) None 
    else Some(trav.min) 
    } 

    def minOptionBy[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = { 
    val trav = asTraversable(coll) 
    if (trav.isEmpty) None 
    else Some(trav.minBy(f)) 
    } 
} 

implicit def extendTraversable[A, C[A] <: TraversableOnce[A]](coll: C[A]): TraversableOnceExt[C[A], A] = 
    new TraversableOnceExt[C[A], A](coll, identity) 

implicit def extendStringTraversable(string: String): TraversableOnceExt[String, Char] = 
    new TraversableOnceExt[String, Char](string, implicitly) 

implicit def extendArrayTraversable[A](array: Array[A]): TraversableOnceExt[Array[A], A] = 
    new TraversableOnceExt[Array[A], A](array, implicitly) 

I następnie napisz po prostu seq.minOptionBy(_.something).

+3

-1 dla odpowiednika pustego bloku catch. Każdy inny wyjątek zostanie również połknięty. – Madoc

+0

@Madoc Fair wystarczająco. Odpowiedź edytowana. –

2

Mało opcją dla każdej większej listy powodu O(nlogn) złożoności:

seq.sortBy(_.something).headOption 
+0

Przerywa brzytwę Ockhama. Jego fragmenty są ostre i niebezpieczne! Pytanie dotyczyło znalezienia maksimum bezpiecznie, a nie sortowania listy. – fatuhoku

+1

@fatuhoku z punktu widzenia matematycznego (i dlatego czystego FP?), 'SortBy' +' headOption' wydaje się być odpowiednikiem 'minByOpt' - dlaczego łamie brzytwę Ockhama? –

+5

@EricAllik Hmmm. Nie mam pojęcia, dlaczego to napisałem. Najwyraźniej nie wiedziałem, co brzytwa Ockhama było kilka lat temu. Haniebny! Teraz mam większe uznanie dla takich odpowiedzi, mimo że zaakceptowana odpowiedź jest lepszym pomysłem. – fatuhoku

6

bezpieczny, kompaktowy i O(n) wersja ze Scalaz:

xs.nonEmpty option xs.minBy(_.foo) 
3

Scala pozwala nam uchwycić błąd z Try. Napiszmy funkcję, która sprawia, że ​​korzystanie z niego:

def min[T <% Ordered[T]](s: Seq[T]) = util.Try(s.min).toOption 

Teraz przetestować że:

scala> min(Seq(1,2,3)) 
res4: Option[Int] = Some(1) 

scala> min(Seq.empty[Int]) 
res5: Option[Int] = None 
0

Mam ten sam problem przed, więc rozciąga zamówione i realizować funkcję porównania. tutaj jest przykład:

case class Point(longitude0: String, latitude0: String) extends Ordered [Point]{ 

    def this(point: Point) = this(point.original_longitude,point.original_latitude) 
    val original_longitude = longitude0 
    val original_latitude = latitude0 

    val longitude = parseDouble(longitude0).get 
    val latitude = parseDouble(latitude0).get 

    override def toString: String = "longitude: " +original_longitude +", latitude: "+ original_latitude 

    def parseDouble(s: String): Option[Double] = try { Some(s.toDouble) } catch { case _ => None } 

    def distance(other: Point): Double = 
    sqrt(pow(longitude - other.longitude, 2) + pow(latitude - other.latitude, 2)) 

override def compare(that: Point): Int = { 
    if (longitude < that.longitude) 
    return -1 
    else if (longitude == that.longitude && latitude < that.latitude) 
    return -1 
    else 
    return 1 
} 
} 

więc jeśli mam nast Punktu mogę prosić o max lub min Metoda

var points = Seq[Point]() 

val maxPoint = points.max 
val minPoint = points.min 
Powiązane problemy