Skip to content

Instantly share code, notes, and snippets.

@mpkocher
Last active October 2, 2019 14:58
Show Gist options
  • Save mpkocher/299bb377e90fa814bc628b6935070d95 to your computer and use it in GitHub Desktop.
Save mpkocher/299bb377e90fa814bc628b6935070d95 to your computer and use it in GitHub Desktop.
Example Monoid with Cats
// Run with ammonite use amm ExampleCatsMonoid.sc
// import $ivy.`org.typelevel::cats-core:1.0.1`
import cats.implicits._
import cats.instances.all._
import cats.Monoid
// For demonstration purposes, load a file (csv, json) and
// each item can be converted to a record of type R
case class R(name: String, age: Int, favoriteColor: String)
// Stat Monoid (similar to Agg)
case class Stat(numTotalRecord: Int, total: Int, max: BigDecimal, min: BigDecimal, uniqueValues: Set[Int] = Set.empty[Int]) {
def numUniqueValues: Int = uniqueValues.size
def mean: BigDecimal = {
numTotalRecord match {
case 0 => 0
case _ => total / numTotalRecord
}
}
def summary: String =
s"""
| total records: $numTotalRecord
| mean: $mean
| max: $max
| min: $min
| num unique values: $numUniqueValues
""".stripMargin
}
val exampleRecords = Seq(
R("Ralph", 12, "red"),
R("Susie", 12, "black"),
R("Eve", 14, "red"),
R("Adam", 15, "blue"),
R("John", 61, "yellow")
)
def exampleIterator(): Iterator[R] = Iterator[R](exampleRecords:_*)
def recordToStat(r: R): Stat = {
Stat(1, r.age, r.age, r.age, Set(r.age))
}
implicit val statsM: Monoid[Stat] = new Monoid[Stat] {
override def empty: Stat = Stat(0, 0, 0.0, 0.0)
override def combine(x: Stat, y: Stat): Stat = {
Stat(x.numTotalRecord + y.numTotalRecord,
x.total + y.total,
Seq(x.max, y.max).max,
Seq(x.min, y.min).min,
x.uniqueValues ++ y.uniqueValues
)
}
}
/**
* Fundamentally, Stat is only well defined
* for non-empty lists/stream, hence this
* needs a custom bootstrapping step to get
* the Monoid.empty from head
*
* Note, this could be a Stream, using a List
* for simplicity
*/
def computeStats(items: Seq[R]): Option[Stat] = {
items match {
case Nil => None
case head::tail =>
Some(
tail
.map(recordToStat)
.foldLeft(recordToStat(head))(Monoid[Stat].combine))
}
}
// This uses a list but could be modified to be a Stream
val stats = computeStats(exampleRecords)
println(stats.map(_.summary))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment