Last active
October 9, 2015 11:19
-
-
Save fancellu/6e647fbf4d39cfd270d0 to your computer and use it in GitHub Desktop.
Checkout Kata CodeTest in Scala
This file contains hidden or 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
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. |
This file contains hidden or 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
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)} | |
} |
This file contains hidden or 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
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 | |
} | |
} |
This file contains hidden or 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
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