Skip to content

Commit

Permalink
Add syntax for minimumBy/maximumBy/Option (#3084)
Browse files Browse the repository at this point in the history
* `mimimumByOption` / `maximumByOption` on `Foldable`
  * `minumumBy` / `maximumBy` on `Reducible`
  • Loading branch information
joroKr21 authored and kailuowang committed Sep 30, 2019
1 parent 166ef81 commit 87cd258
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 0 deletions.
28 changes: 28 additions & 0 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,34 @@ import Foldable.sentinel
def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] =
reduceLeftOption(fa)(A.max)

/**
* Find the minimum `A` item in this structure according to an `Order.by(f)`.
*
* @return `None` if the structure is empty, otherwise the minimum element
* wrapped in a `Some`.
*
* @see [[Reducible#minimum]] for a version that doesn't need to return an
* `Option` for structures that are guaranteed to be non-empty.
*
* @see [[maximumOptionBy]] for maximum instead of minimum.
*/
def minimumOptionBy[A, B: Order](fa: F[A])(f: A => B)(implicit F: Foldable[F]): Option[A] =
F.minimumOption(fa)(Order.by(f))

/**
* Find the maximum `A` item in this structure according to an `Order.by(f)`.
*
* @return `None` if the structure is empty, otherwise the maximum element
* wrapped in a `Some`.
*
* @see [[Reducible#maximum]] for a version that doesn't need to return an
* `Option` for structures that are guaranteed to be non-empty.
*
* @see [[minimumOptionBy]] for minimum instead of maximum.
*/
def maximumOptionBy[A, B: Order](fa: F[A])(f: A => B)(implicit F: Foldable[F]): Option[A] =
F.maximumOption(fa)(Order.by(f))

/**
* Get the element at the index of the `Foldable`.
*/
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ import simulacrum.typeclass
def maximum[A](fa: F[A])(implicit A: Order[A]): A =
reduceLeft(fa)(A.max)

/**
* Find the minimum `A` item in this structure according to an `Order.by(f)`.
*
* @see [[maximumBy]] for maximum instead of minimum.
*/
def minimumBy[A, B: Order](fa: F[A])(f: A => B)(implicit F: Reducible[F]): A =
F.minimum(fa)(Order.by(f))

/**
* Find the maximum `A` item in this structure according to an `Order.by(f)`.
*
* @see [[minimumBy]] for minimum instead of maximum.
*/
def maximumBy[A, B: Order](fa: F[A])(f: A => B)(implicit F: Reducible[F]): A =
F.maximum(fa)(Order.by(f))

/**
* Intercalate/insert an element between the existing elements while reducing.
*
Expand Down
14 changes: 14 additions & 0 deletions tests/src/test/scala/cats/tests/FoldableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,20 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)(implicit ArbFInt: Arb
}
}

test(s"Foldable[$name].maximumBy/minimumBy") {
forAll { (fa: F[Int], f: Int => Int) =>
val maxOpt = fa.maximumOptionBy(f).map(f)
val minOpt = fa.minimumOptionBy(f).map(f)
val nelOpt = fa.toList.toNel
maxOpt should ===(nelOpt.map(_.maximumBy(f)).map(f))
maxOpt should ===(nelOpt.map(_.toList.maxBy(f)).map(f))
minOpt should ===(nelOpt.map(_.minimumBy(f)).map(f))
minOpt should ===(nelOpt.map(_.toList.minBy(f)).map(f))
maxOpt.forall(i => fa.forall(f(_) <= i)) should ===(true)
minOpt.forall(i => fa.forall(f(_) >= i)) should ===(true)
}
}

test(s"Foldable[$name].reduceLeftOption/reduceRightOption") {
forAll { (fa: F[Int]) =>
val list = fa.toList
Expand Down

0 comments on commit 87cd258

Please sign in to comment.