it-swarm.com.de

Min/Max mit Option [T] für eventuell leere Seq?

Ich mache ein bisschen Scala-Gymnastik, wo ich Seq[T] habe, in dem ich versuche, das "kleinste" Element zu finden. Das mache ich gerade:

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

Es funktioniert gut, aber ich bin nicht ganz zufrieden - es ist ein bisschen lang für so eine einfache Sache, und Ich kümmere mich nicht besonders um "if" s . Die Verwendung von minBy wäre viel eleganter:

val least = seq.minBy(_.something)

... aber min und minBy werfen Ausnahmen aus, wenn die Sequenz leer ist. Gibt es eine idiomatischere, elegantere Methode, das kleinste Element einer möglicherweise leeren Liste als Option zu finden? 

47
gustafc

Das Starten von Scala 2.13, minByOption / maxByOption ist jetzt Teil der Standardbibliothek und gibt None zurück, wenn die Sequenz leer ist:

seq.minByOption(_.something)
List((3, 'a'), (1, 'b'), (5, 'c')).minByOption(_._1) // Option[(Int, Char)] = Some((1,b))
List[(Int, Char)]().minByOption(_._1)                // Option[(Int, Char)] = None
9
Xavier Guihot
seq.reduceOption(_ min _)

macht was du willst


Bearbeiten: Hier ist ein Beispiel, das Ihren _.something enthält:

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

oder als generische Methode:

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

die Sie mit minOptionBy(seq)(_.something) aufrufen könnten

66
Luigi Plinge

Eine sichere, kompakte und O(n)-Version mit Scalaz:

xs.nonEmpty option xs.minBy(_.foo)
8
Erik Allik

Kaum eine Option für eine größere Liste aufgrund der O(nlogn)-Komplexität:

seq.sortBy(_.something).headOption
4

Wie wäre es damit?

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

Oder ausführlicher, wenn Sie keine anderen Ausnahmen schlucken möchten:

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

Alternativ können Sie alle Sammlungen mit dem folgenden Element versehen:

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)

Und dann schreibe einfach seq.minOptionBy(_.something).

Sie könnten immer so etwas tun:

case class Foo(num: Int)

val foos: Seq[Foo] = Seq(Foo(1), Foo(2), Foo(3))
val noFoos: Seq[Foo] = Seq.empty

def minByOpt(foos: Seq[Foo]): Option[Foo] =
  foos.foldLeft(None: Option[Foo]) { (acc, elem) => 
    Option((elem +: acc.toSeq).minBy(_.num)) 
  }

Dann verwenden Sie wie:

scala> minByOpt(foos)
res0: Option[Foo] = Some(Foo(1))

scala> minByOpt(noFoos)
res1: Option[Foo] = None
0
user451151

Ich habe das gleiche Problem vor, also erweitere ich bestellte und implementiere die Compare-Funktion.

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

wenn ich also einen Punkt von Punkt habe, kann ich nach max- oder min-Methode fragen

  var points =  Seq[Point]()

val maxPoint = points.max
val minPoint = points.min
0
Dvir Arad