Skip to content

Instantly share code, notes, and snippets.

@infertux
Created June 14, 2011 19:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save infertux/1025651 to your computer and use it in GitHub Desktop.
Save infertux/1025651 to your computer and use it in GitHub Desktop.
Code kata
class CheckOut
def initialize(rules)
@items = Hash.new { 0 }
@pricer = Pricer.new(rules)
end
def scan(sku)
@items[sku] += 1
end
def total()
total = 0
@items.each { |sku, quantity| total += @pricer.price(sku, quantity) }
total
end
end
class Pricer
def initialize(rules)
@rules = rules
end
def price(sku, quantity)
total = 0
while quantity > 0 do
deal_quantity, deal_price = find_best_deal(sku, quantity)
total += deal_price
quantity -= deal_quantity
end
total
end
private
def find_best_deal(sku, quantity)
deal = nil
prices = @rules[sku]
raise IndexError, "No price found for #{sku}." if prices.nil?
prices.each do |rule_quantity, rule_total|
if quantity >= rule_quantity then
deal = [rule_quantity, rule_total]
break
end
end
raise ArgumentError, "No price found for #{quantity} of #{sku}." if deal.nil?
deal
end
end
class CSVRulesParser
def self.parse(rules)
prices = {}
rules.each do |line|
line.chomp!
cur_sku, quantity, total = line.split(",")
prices[cur_sku] ||= PriceList.new
prices[cur_sku][quantity.to_i] = total.to_i
end
prices
end
end
# Warning: not efficient with huge lists because the array is sorted after each insertion.
# In our case, it's fine because there should be no more than 2 or 3 rules per SKU.
class PriceList
def initialize()
@list = []
end
def [](quantity)
@list[quantity]
end
def []=(quantity, price)
@list.reject! {|k,v| k == quantity}
@list << [quantity, price]
@list = @list.sort_by {|k,v| k}.reverse
end
def each(&block)
@list.each(&block)
end
end
# A sample set of prices in CSV format
# The rows could be in any order
PRICES = [
"A,3,130",
"A,1,50",
"B,1,30",
"D,1,15",
"C,1,20",
"B,2,45",
# More rules
"E,2,20", # you can't buy only one 'E' (just to throw an exception ;))
"E,3,28",
"E,4,35",
"E,10,1" # a very good deal
]
require 'checkout'
require 'test/unit'
# Tests from http://codekata.pragprog.com/2007/01/kata_nine_back_.html
class TestPrice < Test::Unit::TestCase
# Just a bit of parsing added to the original test code
def setup()
@rules = CSVRulesParser.parse(PRICES)
end
def price(goods)
co = CheckOut.new(@rules)
goods.split(//).each { |item| co.scan(item) }
co.total
end
def test_totals
assert_equal( 0, price(""))
assert_equal( 50, price("A"))
assert_equal( 80, price("AB"))
assert_equal(115, price("CDBA"))
assert_equal(100, price("AA"))
assert_equal(130, price("AAA"))
assert_equal(180, price("AAAA"))
assert_equal(230, price("AAAAA"))
assert_equal(260, price("AAAAAA"))
assert_equal(160, price("AAAB"))
assert_equal(175, price("AAABB"))
assert_equal(190, price("AAABBD"))
assert_equal(190, price("DABABA"))
# More test cases
assert_equal( 2*130 + 45 + 30, price("AAABABBAA"))
assert_equal( 2*130 + 3*45, price("ABABABABABAB"))
assert_equal(130 + 45+30 + 3*20 + 3*15, price("ABCDABCDABCD"))
assert_raise(ArgumentError) { price("E") }
assert_equal( 20, price("EE"))
assert_equal( 28, price("EEE"))
assert_equal( 35, price("EEEE"))
assert_equal( 1, price("EEEEEEEEEE"))
assert_equal(2*35, price("EEEEEEEE"))
assert_equal(1+20, price("EEEEEEEEEEEE"))
assert_equal( 2*1, price("EEEEEEEEEEEEEEEEEEEE"))
assert_raise(IndexError) { price("X") }
end
def test_incremental
co = CheckOut.new(@rules)
assert_equal( 0, co.total)
co.scan("A"); assert_equal( 50, co.total)
co.scan("B"); assert_equal( 80, co.total)
co.scan("A"); assert_equal(130, co.total)
co.scan("A"); assert_equal(160, co.total)
co.scan("B"); assert_equal(175, co.total)
# More test cases
co.scan("D"); assert_equal(190, co.total)
co.scan("X"); assert_raise(IndexError) { co.total }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment