Last active
October 2, 2019 14:58
-
-
Save mpkocher/299bb377e90fa814bc628b6935070d95 to your computer and use it in GitHub Desktop.
Example Monoid with Cats
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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