Min / max avec Option[T] pour éventuellement les Seq vides?

je fais un peu de gymnastique Scala là où j'ai Seq[T] dans lequel j'essaie de trouver le "petit" élément. C'est ce que je fais maintenant:

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

Il fonctionne bien, mais je ne suis pas tout à fait convaincu - c'est un peu long pour une chose si simple, et je n'ai pas beaucoup de soins pour "si"s. En utilisant minBy serait beaucoup plus élégant:

val least = seq.minBy(_.something)

... mais min et minBy lancer des exceptions lorsque la séquence est vide. Y a-t-il une façon idiomatique, plus élégante de trouver le plus petit élément d'une liste (éventuellement vide comme un <!--6?

45
demandé sur Xavier Guihot 2012-06-07 01:02:20

7 réponses

démarrage Scala 2.13, minByOption/maxByOption fait maintenant partie de la bibliothèque standard et retourne None si la séquence est vide:

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
1
répondu Xavier Guihot 2018-10-02 20:50:56
seq.reduceOption(_ min _)

est-ce que vous voulez?


Edit: voici un exemple incorporant votre _.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))

ou, comme méthode générique:

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

que vous pouvez invoquer avec minOptionBy(seq)(_.something)

58
répondu Luigi Plinge 2012-06-07 00:14:16

Un coffre-fort, compact et O(n) version avec Scalaz:

xs.nonEmpty option xs.minBy(_.foo)
7
répondu Erik Allik 2015-02-26 14:31:28

pas vraiment une option pour une liste plus large à cause de O(nlogn) complexité:

seq.sortBy(_.something).headOption
3
répondu Tomasz Nurkiewicz 2012-06-06 21:27:28

Comment à ce sujet?

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

Ou, plus détaillé, si vous ne voulez pas avaler d'autres exceptions:

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

alternativement, vous pouvez maquiller toutes les collections avec quelque chose comme ceci:

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)

Et puis il suffit d'écrire seq.minOptionBy(_.something).

1
répondu Jean-Philippe Pellet 2012-06-07 08:39:36

j'ai le même problème avant, donc j'étend la fonction ordonnée et implémente la fonction comparer. ici est un exemple:

 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
 }
}
  var points =  Seq[Point]()

val maxPoint = points.max
val minPoint = points.min
0
répondu Dvir Arad 2017-08-27 13:22:08

à Haskell vous envelopperiez le minimumBy appel

least f x | Seq.null x = Nothing
          | otherwise  = Just (Seq.minimumBy f x) 
-3
répondu Don Stewart 2012-06-06 21:07:00