(1) Ordering#compare
promises to denote the three possible results by a negative, positive, or zero number, not -1, 1, or 0 necessarily.
(2) Option#fold
is generally (though not universally) considered to be more idiomatic than pattern matching.
(3) You are calling f
possibly multiple times per element. TraversableOnce#maxBy
used to do this before it was fixed in 2.11.
(4) You only accept Seq
. The Scala library works hard to use CanBuildFrom
to generalize the algorithms; you might want to as well.
(5) You can use the syntactic sugar B : Ordering
if you would like.
(6) You prepend to the Seq
. This is faster than appending, since prepending is O(1) for List
and appending is O(n). But you wind up with the results in reverse order. foldRight
will correct this. (Or you can call reverse
on the final result.)
If you want to allow the use of CanBuildFrom
,
def maxBy[A, Repr, That, B : Ordering](elements: TraversableLike[A, Repr])(f: A => B)(implicit bf: CanBuildFrom[Repr, A, That]): That = {
val b = bf()
elements.foldLeft(Option.empty[B]) { (best, element) =>
val current = f(element)
val result = best.fold(0)(implicitly[Ordering[B]].compare(current, _))
if (result > 0) {
b.clear()
}
if (result >= 0) {
b += element
Some(current)
} else {
best
}
}
b.result
}
If you want to work with TraversableOnce
,
def maxBy[A, B : Ordering](elements: TraversableOnce[A])(f: A => B): Seq[A] = {
elements.foldRight((Option.empty[B], List.empty[A])) { case (element, (best, elements)) =>
val current = f(element)
val result = best.fold(0)(implicitly[Ordering[B]].compare(current, _))
if (result > 0) {
(Some(current), List(element))
} else {
(best, if (result == 0) element +: elements else elements)
}
}._2
}