Skip to content

Commit

Permalink
backported #3084 Add minimumBy/maximumBy/Option to Foldable
Browse files Browse the repository at this point in the history
  • Loading branch information
gagandeepkalra authored and travisbrown committed Feb 19, 2020
1 parent 5d97f98 commit d278b04
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/syntax/foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,34 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal {
import cats.syntax.foldable._
F.partitionEitherM[G, A, B, C](fa)(f)(A, M)
}

/**
* 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[B: Order](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[B: Order](f: A => B)(implicit F: Foldable[F]): Option[A] =
F.maximumOption(fa)(Order.by(f))
}

final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal {
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/cats/syntax/reducible.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,20 @@ final class ReducibleOps0[F[_], A](private val fa: F[A]) extends AnyVal {
* */
def reduceMapK[G[_], B](f: A => G[B])(implicit F: Reducible[F], G: SemigroupK[G]): G[B] =
F.reduceLeftTo(fa)(f)((b, a) => G.combineK(b, f(a)))

/**
* Find the minimum `A` item in this structure according to an `Order.by(f)`.
*
* @see [[maximumBy]] for maximum instead of minimum.
*/
def minimumBy[B: Order](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[B: Order](f: A => B)(implicit F: Reducible[F]): A =
F.maximum(fa)(Order.by(f))
}
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 d278b04

Please sign in to comment.