Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Min/max with Option[T] for possibly empty Seq?

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?

like image 445
gustafc Avatar asked Jun 06 '12 21:06

gustafc


5 Answers

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)

like image 139
Luigi Plinge Avatar answered Nov 10 '22 22:11

Luigi Plinge


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
like image 37
Xavier Guihot Avatar answered Nov 10 '22 22:11

Xavier Guihot


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

xs.nonEmpty option xs.minBy(_.foo)
like image 35
Erik Kaplun Avatar answered Nov 10 '22 22:11

Erik Kaplun


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

seq.sortBy(_.something).headOption
like image 6
Tomasz Nurkiewicz Avatar answered Nov 10 '22 21:11

Tomasz Nurkiewicz


Also, it is available to do like that

Some(seq).filter(_.nonEmpty).map(_.minBy(_.something))
like image 4
Anar Amrastanov Avatar answered Nov 10 '22 20:11

Anar Amrastanov