Skip to content

Commit

Permalink
Add extrema, extremaOf, and extremaBy to Foldable
Browse files Browse the repository at this point in the history
Single-pass operations to determine the extrema of Foldable values.
  • Loading branch information
Kris Nuttycombe committed May 19, 2017
1 parent f23d2f2 commit 5d1a9b3
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 0 deletions.
34 changes: 34 additions & 0 deletions core/src/main/scala/scalaz/Foldable.scala
Expand Up @@ -228,6 +228,40 @@ trait Foldable[F[_]] { self =>
case (Some(x @ (a, b)), aa) => val bb = f(aa); some(if (Order[B].order(b, bb) == LT) x else aa -> bb)
} map (_._1)

/** The smallest and largest elements of `fa` or None if `fa` is empty */
def extrema[A: Order](fa: F[A]): Option[(A, A)] =
extremaBy(fa)(identity)

/** The smallest and largest values of `f(a)` for each element `a` of `fa` , or None if `fa` is empty */
def extremaOf[A, B: Order](fa: F[A])(f: A => B): Option[(B, B)] =
foldLeft(fa, none[(B, B)]) {
case (None, a) =>
val b = f(a)
some((b, b))
case (Some(x @ (bmin, bmax)), a) =>
val b = f(a)
some(
if (Order[B].order(b, bmin) == LT) (b, bmax)
else if (Order[B].order(b, bmax) == GT) (bmin, b)
else x
)
}

/** The elements (amin, amax) of `fa` withc yield the smallest and largest values of `f(a)`, respectively, or None if `fa` is empty */
def extremaBy[A, B: Order](fa: F[A])(f: A => B): Option[(A, A)] =
foldLeft(fa, none[((A, A), (B, B))]) {
case (None, a) =>
val b = f(a)
some((a, a) -> (b, b))
case (Some(x @ ((amin, amax), (bmin, bmax))), a) =>
val b = f(a)
some(
if (Order[B].order(b, bmin) == LT) (a, amax) -> (b, bmax)
else if (Order[B].order(b, bmax) == GT) (amin, a) -> (bmin, b)
else x
)
} map (_._1)

def sumr[A](fa: F[A])(implicit A: Monoid[A]): A =
foldRight(fa, A.zero)(A.append)

Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/scalaz/syntax/FoldableSyntax.scala
Expand Up @@ -58,6 +58,9 @@ final class FoldableOps[F[_],A] private[syntax](val self: F[A])(implicit val F:
final def minimum(implicit A: Order[A]): Option[A] = F.minimum(self)
final def minimumOf[B: Order](f: A => B): Option[B] = F.minimumOf(self)(f)
final def minimumBy[B: Order](f: A => B): Option[A] = F.minimumBy(self)(f)
final def extrema(implicit A: Order[A]): Option[(A, A)] = F.extrema(self)
final def extremaOf[B: Order](f: A => B): Option[(B, B)] = F.extremaOf(self)(f)
final def extremaBy[B: Order](f: A => B): Option[(A, A)] = F.extremaBy(self)(f)
final def distinct(implicit A: Order[A]): IList[A] = F.distinct(self)
final def distinctE(implicit A: Equal[A]): IList[A] = F.distinctE(self)
final def longDigits(implicit d: A <:< Digit): Long = F.longDigits(self)
Expand Down
15 changes: 15 additions & 0 deletions tests/src/test/scala/scalaz/FoldableTest.scala
@@ -1,6 +1,7 @@
package scalaz

import std.AllInstances._
import syntax.apply._
import syntax.foldable._
import syntax.equal._
import org.scalacheck.Prop.forAll
Expand Down Expand Up @@ -58,6 +59,20 @@ object FoldableTest extends SpecLite {
else
(xs minimumBy f) must_== Some((xs zip (xs map f)).minBy(_._2)._1)
}
"extrema" ! forAll {
(xs: List[Int]) =>
(xs.extrema) must_== xs.minimum.tuple(xs.maximum)
}
"extremaOf" ! forAll {
(xs: List[Int]) =>
val f: Int => Double = 1D + _
(xs extremaOf f) must_== (xs minimumOf f).tuple(xs maximumOf f)
}
"extremaBy" ! forAll {
(xs: List[Int]) =>
val f: Int => Double = 1D + _
(xs extremaBy f) must_== (xs minimumBy f).tuple(xs maximumBy f)
}

"distinct" ! forAll {
(xs: List[Int]) =>
Expand Down

0 comments on commit 5d1a9b3

Please sign in to comment.