Code Kata - 9
Code Kata 9
Back to the supermarket. This week, we’ll implement the code for a checkout system that handles pricing schemes such as apples cost 50 cents, three apples cost \$ 1.30. Way back in KataOne we thought about how to model the various options for supermarket pricing. We looked at things such as “three for a dollar,\$1.99 per pound,” and “buy two, get one free.” This week, let’s 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). Our goods are priced individually. In addition, some items are multipriced: buy n of them, and they’ll cost you y cents. For example, item A might cost 50 cents 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:
Our checkout accepts items in any order, so that if we scan a B, an A, and another B, we’ll recognize 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.
Check out process = No Abstraction
> prices <- data.frame(items = LETTERS, price = sample.int(100, 26, replace = T), qty = sample.int(10, 26, replace = T), discount = 0) > prices$discount <- round(with(prices, qty * price * (1 - runif(26)))) > print(prices) items price qty discount 1 A 26 7 44 2 B 90 7 154 3 C 41 5 201 4 D 90 10 313 5 E 93 9 121 6 F 96 3 240 7 G 70 6 134 8 H 55 3 15 9 I 15 1 2 10 J 14 9 87 11 K 79 8 542 12 L 67 7 107 13 M 4 8 11 14 N 15 5 7 15 O 53 6 168 16 P 18 1 15 17 Q 68 5 140 18 R 31 2 25 19 S 8 2 15 20 T 55 5 261 21 U 42 10 115 22 V 70 9 547 23 W 13 9 107 24 X 24 9 21 25 Y 36 2 26 26 Z 51 2 86 > n <- 100 > cart <- sample(LETTERS, n, replace = T) > process <- table(cart) > process <- data.frame(bought = names(process), qty.bought = c(process)) > process <- merge(process, prices, by.x = "bought", by.y = "items", all.x = TRUE) > process$cost <- with(process, discount * (qty.bought%/%qty) + price * (qty.bought%%qty)) > print(process) bought qty.bought price qty discount cost 1 A 3 26 7 44 78 2 B 2 90 7 154 180 3 C 3 41 5 201 123 4 D 5 90 10 313 450 5 E 7 93 9 121 651 6 F 5 96 3 240 432 7 G 3 70 6 134 210 8 H 4 55 3 15 70 9 I 5 15 1 2 10 10 J 2 14 9 87 28 11 K 6 79 8 542 474 12 L 4 67 7 107 268 13 M 4 4 8 11 16 14 N 6 15 5 7 22 15 O 5 53 6 168 265 16 P 2 18 1 15 30 17 Q 4 68 5 140 272 18 R 1 31 2 25 31 19 S 2 8 2 15 15 20 T 7 55 5 261 371 21 U 3 42 10 115 126 22 V 4 70 9 547 280 23 W 3 13 9 107 39 24 X 2 24 9 21 48 25 Y 1 36 2 26 36 26 Z 7 51 2 86 309 > cat(" The total cost is ", sum(process$cost), "\n") The total cost is 4834 |
Now let me replicate the exact data given in the codekata
> prices <- data.frame(items = LETTERS[1:4], price = c(50, 30, 20, 15), qty = c(3, 2, 1, 1), discount = c(130, 45, 20, 15)) > print(prices) items price qty discount 1 A 50 3 130 2 B 30 2 45 3 C 20 1 20 4 D 15 1 15 > test.items <- c("", "A", "AB", "CDBA", "AA", "AAA", "AAAA", "AAAAA", "AAAAAA", "AAAB", "AAABB", "AAABBD", "DABABA") > test.res <- c(0, 50, 80, 115, 100, 130, 180, 230, 260, 160, 175, 190, 190) > getCost <- function(cart) { if (nchar(cart) == 0) { return(0) } cart <- strsplit(cart, "")[[1]] process <- table(cart) process <- data.frame(bought = names(process), qty.bought = c(process)) process <- merge(process, prices, by.x = "bought", by.y = "items", all.x = TRUE) process$cost <- with(process, discount * (qty.bought%/%qty) + price * (qty.bought%%qty)) return(sum(process$cost)) } > res <- cbind(sapply(test.items, getCost)) > for (i in seq_along(res)) { expect_that(res[i], equals(test.res[i])) } > testIncremental <- function(x) { cart <<- paste(cart, x, sep = "") return(getCost(cart)) } > cart <- "" > expect_that(testIncremental(""), equals(0)) > expect_that(testIncremental("A"), equals(50)) > expect_that(testIncremental("B"), equals(80)) > expect_that(testIncremental("A"), equals(130)) > expect_that(testIncremental("A"), equals(160)) > expect_that(testIncremental("B"), equals(175)) |
I have implemented a very basic non abstracted version of the code. I need to look at design patterns. Actually I have forgotten all design patterns from Mark Joshi’s book. I need to go over the book some time. One of the comments says Decorator patttern is used. I have to understand what that is.
Here is a comment that has made me curious to go over this codekata more thoroughly
I finally uploaded a 20" screencast for the Checkout-Kata: http://www.vimeo.com/11604377 It was the result of a Kata session we did at the Jax-conference in germany this year. There are thousand TDD-aspects i’d still love to improve, but i like the decoupling of cart and rules in this implementation. Basically the cart allows the rules to select which items they want to bill. Nice side effect: the cart items (“A”, “B”,… in my case) can be replaced by any other object and the code should still work. I did this Kata about 20-25 times with 3 or 4 different approaches.
Learnings
- Lessons from screencast and implementing the same in R
- Need to revise Design Patterns