2012-06-06 12 views
41

Sto facendo un po 'di ginnastica alla Scala dove ho lo Seq[T] nel quale cerco di trovare l'elemento "più piccolo". Questo è quello che faccio adesso:scala - Min/max con Opzione [T] per eventuale Seq? Vuoto?

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

Funziona bene, ma non sono abbastanza soddisfatto - è un po 'lungo per una cosa così semplice, e I don't care much for "if"s. Utilizzando minBy sarebbe molto più elegante:

val least = seq.minBy(_.something) 

... ma min e minBy generano eccezioni quando la sequenza è vuota. Esiste un modo idiomatico e più elegante di trovare l'elemento più piccolo di una lista potenzialmente vuota come Option?

risposta

52
seq.reduceOption(_ min _) 

fa ciò che vuoi?


Edit: Ecco un esempio che incorporano il _.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)) 

o, come metodo generico:

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

che si potrebbe richiamare con minOptionBy(seq)(_.something)

+2

è anche possibile usare 'seq reductionOption math.min'. Questo è più efficiente perché non richiede una conversione implicita. – sschaef

+0

@Antoras buona idea, ma credo che le differenze siano in pratica ottimizzate - almeno, questo è quello che mostra il mio microbenchmark –

-3

In Haskell si sarebbe avvolgere il minimumBy chiamata come

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

C'è un modo per _catch_ l'errore lanciato da 'minimumBy'? – missingfaktor

+0

Eviterei l'eccezione in primo luogo - usa la guardia per escludere la possibilità che si verifichi l'eccezione. Questo è semanticamente più ordinato di appoggiarsi alla semantica delle eccezioni per darti un valore opzionale. –

+3

Haskell è cool, senza dubbio. Ma "questo è come puoi farlo in un altro linguaggio di programmazione" non risponde alla domanda. – Utgarda

1

ne dici di questo?

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

Oppure, più dettagliato, se non si vuole ingoiare altre eccezioni:

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

In alternativa, è possibile magnaccia tutte le collezioni con qualcosa di simile:

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) 

E quindi scrivi semplicemente seq.minOptionBy(_.something).

+3

-1 per l'equivalente di un blocco di cattura vuoto. Anche qualsiasi altra eccezione generata verrà inghiottita. – Madoc

+0

@Madoc Giusto abbastanza. Risposta modificata. –

2

Difficilmente un'opzione per ogni lista più grande a causa della complessità O(nlogn):

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

Rompe il rasoio di Occam. I suoi frammenti sono taglienti e pericolosi! La domanda era trovare il massimo in modo sicuro, non ordinare la lista. – fatuhoku

+1

@fatuhoku da un punto di vista matematico (e quindi puro?), 'SortBy' +' headOption' sembra equivalente a 'minByOpt' - perché rompere il rasoio di Occam? –

+5

@EricAllik Hmmm. Non ho idea del motivo per cui l'ho scritto. Evidentemente non sapevo cosa fosse il rasoio di Occam un paio di anni fa. Vergognoso! Ora ho più di un apprezzamento per le risposte come questa, nonostante la risposta accettata sia un'idea migliore. – fatuhoku

6

Una cassetta di sicurezza, compatto e la versione O(n) con Scalaz:

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

Scala consente di acquisire l'errore con Try. Scriviamo una funzione che fa uso di esso:

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

Ora diamo prova che:

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

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

Ho lo stesso problema prima, così mi si estende verso ordinato e implementare la funzione di confronto. ecco esempio:

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

così se ho un seguenti del Point posso chiedere per max o il metodo min

var points = Seq[Point]() 

val maxPoint = points.max 
val minPoint = points.min