Skip to content

Instantly share code, notes, and snippets.

@fancellu
Last active October 9, 2015 11:19
Show Gist options
  • Save fancellu/6e647fbf4d39cfd270d0 to your computer and use it in GitHub Desktop.
Save fancellu/6e647fbf4d39cfd270d0 to your computer and use it in GitHub Desktop.
Checkout Kata CodeTest in Scala
Checkout Kata
Implement the code for a supermarket checkout that calculates the total price of a number of
items. In a normal supermarket, things are identified using Stock Keeping Units, or SKUs. In our
store, we’ll use individual letters of the alphabet (A, B, C, and so on as the SKUs). Our goods
are priced individually. In addition, some items are multi-priced: buy n of them, and they’ll cost
you y. For example, item ‘A’ might cost 50 pence individually, but this week we have a special
offer: buy three ‘A’s and they’ll cost you £1.30. In fact this week’s prices are:
Item Unit Price Special Price
A 50 3 for 130
B 30 2 for 45
C 20
D 15
Our checkout accepts items in any order, so that if we scan a B, an A, and another B, we’ll
recognise the two B’s and price them at 45 (for a total price so far of 95). Because the pricing
changes frequently, we need to be able to pass in a set of pricing rules each time we start
handling a checkout transaction.
object Checkout extends App {
val rulesString = """
A,50,3 for 130
B,30,2 for 45
C,20
D,15
"""
import CheckoutRuleEngine._
val rulesMap = getRulesMap(rulesString)
println(total(rulesMap, List("A", "A", "B", "C", "D", "A")))
println(total(rulesMap, List("A", "B", "C", "D", "A")))
println(total(rulesMap, List()))
val rulesMap2 = getRulesMap("A,100,2 for 180")
println(total(rulesMap2, List("A", "A", "A")))
import scala.util.Try
Try{println(total(rulesMap2, List("A", "A", "A","BADSKU")))}.recover{case f=>println(f.getMessage)}
Try{println(total(getRulesMap("bad rules"), List("A", "A", "A","BADSKU")))}.recover{case f=>println(f.getMessage)}
Try{println(total(getRulesMap("A,100,2 for 180xxx"), List("A")))}.recover{case f=>println(f.getMessage)}
}
object CheckoutRuleEngine {
type SKU = String
type RulesMap = Map[SKU, Rule]
case class FormatException(message:String) extends Exception(message)
case class UnknownSKUException(message:String) extends Exception(message)
sealed trait Rule {
val sku: SKU
def subtotal(count: Int): Double
}
private case class Simple(sku: SKU, unitPrice: Double) extends Rule {
def subtotal(count: Int) = count * unitPrice
}
private case class Special(sku: SKU, unitPrice: Double, specialCount: Int, specialPrice: Double) extends Rule {
def subtotal(count: Int) = count / specialCount * specialPrice + count % specialCount * unitPrice
}
def getRulesMap(string: String): RulesMap = {
def parseRule(string: String): (SKU, Rule) = {
val special = """(.+) for (.+)$""".r
string.split(",") match {
case Array(sku, unitPrice) => sku -> Simple(sku, unitPrice.toDouble)
case Array(sku, unitPrice, special(specialCount, specialPrice)) => sku -> Special(sku, unitPrice.toDouble, specialCount.toInt, specialPrice.toDouble)
case _ => throw FormatException(s"string $string does not obey allowed format")
}
}
val rulePairs = for {
rule <- string.split("\n")
trim = rule.trim if (!trim.isEmpty())
rulePair = parseRule(trim)
} yield rulePair
rulePairs.toMap
}
def total(rulesMap: RulesMap, items: Seq[SKU]) = {
val itemCounts = items.groupBy(identity).mapValues(_.size)
val subtotals = for {
(sku, count) <- itemCounts
rule = rulesMap.getOrElse(sku, throw UnknownSKUException(s"SKU $sku not found in rules"))
} yield rule.subtotal(count)
subtotals.sum
}
}
195.0
165.0
0.0
280.0
SKU BADSKU not found in rules
string bad rules does not obey allowed format
For input string: "180xxx"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment