66

I'm doing a bit of Scala gymnastics where I have Seq[T] in which I try to find the "smallest" element. This is what I do right now:

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

It works fine, but I'm not quite satisfied - it's a bit long for such a simple thing, and I don't care much for "if"s. Using minBy would be much more elegant:

val least = seq.minBy(_.something)

... but min and minBy throw exceptions when the sequence is empty. Is there an idiomatic, more elegant way of finding the smallest element of a possibly empty list as an Option?

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
gustafc
  • 28,465
  • 7
  • 73
  • 99

10 Answers10

79
seq.reduceOption(_ min _)

does what you want?


Edit: Here's an example incorporating your _.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))

or, as generic method:

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

which you could invoke with minOptionBy(seq)(_.something)

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 3
    it is also possible to use `seq reduceOption math.min`. This is more efficient because it doesn't require an implicit conversion. – kiritsuku Jun 07 '12 at 09:39
  • @Antoras good thought, but I believe any differences are in practice optimized away - at least, that's what my microbenchmark shows – Luigi Plinge Jun 07 '12 at 15:32
38

Starting Scala 2.13, minByOption/maxByOption is now part of the standard library and returns None if the sequence is empty:

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
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
9

A safe, compact and O(n) version with Scalaz:

xs.nonEmpty option xs.minBy(_.foo)
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
6

Hardly an option for any larger list due to O(nlogn) complexity:

seq.sortBy(_.something).headOption
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • Breaks Occam's razor. Its fragments are sharp and dangerous! The question was to find the maximum safely, not to sort the list. – fatuhoku Dec 03 '12 at 16:51
  • 3
    @fatuhoku from a mathematical (and therefore pure FP?) point of view, `sortBy` + `headOption` seems like equivalent to `minByOpt` — why does it break Occam's razor? – Erik Kaplun Feb 24 '15 at 07:56
  • 6
    @EricAllik Hmmm. No idea why I wrote that. I clearly didn't know what Occam's razor actually was a couple of years ago. Shameful! Now I have more of an appreciation for answers like this, despite the accepted answer being a better idea. – fatuhoku Feb 24 '15 at 14:37
5

Also, it is available to do like that

Some(seq).filter(_.nonEmpty).map(_.minBy(_.something))
Anar Amrastanov
  • 385
  • 1
  • 3
  • 13
1

How about this?

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

Or, more verbose, if you don't want to swallow other exceptions:

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

Alternatively, you can pimp all collections with something like this:

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)

And then just write seq.minOptionBy(_.something).

Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • 3
    -1 for the equivalent of an empty catch block. Any other exception thrown will be swallowed as well. – Madoc Jun 07 '12 at 08:11
1

I have the same problem before, so I extends Ordered and implement the compare function. here is example:

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

so if I have a seq of Point I can ask for max or min method

  var points =  Seq[Point]()

val maxPoint = points.max
val minPoint = points.min
Dvir Arad
  • 147
  • 1
  • 9
  • 1
    How does that solve the problem that min and max on an empty collection throw an exception? – nafg Apr 19 '18 at 05:15
1

You could always do something like:

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

Then use like:

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

scala> minByOpt(noFoos)
res1: Option[Foo] = None
user451151
  • 406
  • 2
  • 10
1

For scala < 2.13

Try(seq.minBy(_.something)).toOption

For scala 2.13

seq.minByOption(_.something)
-5

In Haskell you'd wrap the minimumBy call as

least f x | Seq.null x = Nothing
          | otherwise  = Just (Seq.minimumBy f x) 
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • I'd avoid the exception in the first place -- use the guard to rule out the chance of the exception occuring. That's semantically neater than leaning on the exception semantics to give you an optional value. – Don Stewart Jun 06 '12 at 21:46
  • 5
    Haskell is cool, no doubt. But "this is how you can do it in another programming language" doesn't answer the question. – Utgarda Jul 08 '15 at 15:24