Skip to content

Instantly share code, notes, and snippets.

@sarvsav
Forked from keithtom/check_out.rb
Last active August 29, 2015 14:06
Show Gist options
  • Save sarvsav/1332140b5ba2a2a3a381 to your computer and use it in GitHub Desktop.
Save sarvsav/1332140b5ba2a2a3a381 to your computer and use it in GitHub Desktop.
require_relative "rules"
# An object which represents the checkout process or cash register.
# It scans items, which are added to an internal list of items,
# which are then used to return the #total at any given moment.
# This total tries to use the rules which lead to the cheapest price.
class CheckOut < Struct.new(:rules)
attr_accessor :rules # list of available pricing rules (passed on initialization)
attr_accessor :items # hash of scanned items and quantities
# { "A" => 0, "B" => 1 } means we have scanned one "A" and zero "B"s
def initialize(*args)
super
@items = Hash.new { 0 } # if an item isn't found, it is assumed the quantity is 0.
end
# increment item counter in items hash.
def scan(item)
@items[item] += 1
puts "scanned 1 #{item}, totaling #{@items[item]}"
end
# calculates grand total by summing each item's total
def total
puts "Calculating grand total..."
# sum the totals for each item
result = @items.inject(0) { |sum, key_val| sum + item_total(key_val.first, key_val.last) }
puts "Grand total is #{result}!"
result
end
# calculate item's total by using the best rules available for the quantity.
def item_total(item, quantity)
item_total = 0 # counter of running total
while quantity != 0
# find the best rule for this item and quantity
rule = Rule.for(item, quantity)
# apply that rule for that quantity
item_total += rule.quantity_required * rule.unit_price
# reduce the quantity, and find the next best rule again, until no more quantity
# assumes we always have a Rule for quantity 1, which is 'normal' price.
quantity -= rule.quantity_required
end
puts "#{item} total: #{item_total}"
item_total
end
end
# An object which represents a Rule, for pricing items at checkout.
# Rules are made up of:
# product_name (in this case "A", "B", "C", "D")
# description (just useful for debugging, and UX)
# unit_price (used to determine the best rule, since this will give the best price)
# quantity_required (the quantity required for the rule to be applicable)
class Rule < Struct.new(:product_name, :description, :unit_price, :quantity_required)
# stores ALL the rules, used for searching which is the best rule.
def self.all
@rules ||= []
end
# add a new rule to the list of rules
def self.add(product_name, description, unit_price, quantity_required)
@rules ||= []
@rules << new(product_name, description, unit_price, quantity_required)
end
# find the best rule from all rules, given a specific item, and a quantity
def self.for(item, quantity)
# filter rules down to ones relevant to the item being purchased
item_rules = all.select { |rule| rule.product.to_s == item.to_s }
puts "found #{item_rules.size} rules for #{item}..."
# filter rules down to ones meeting the quantity required
item_rules = item_rules.select { |rule| rule.quantity_required <= quantity }
puts "#{item_rules.size} rules are applicable..."
# find the rule which provides the best unit price
rule = item_rules.min_by { |rule| rule.unit_price }
puts "Applying rule: #{rule.description}"
rule
end
end
# Initialize the rules according to the kata data
Rule.add "A", "1 for 50", 50/1.0, 1
Rule.add "A", "3 for 130", 130/3.0, 3
Rule.add "B", "1 for 30", 30/1.0, 1
Rule.add "B", "2 for 45", 45/2.0, 2
Rule.add "C", "1 for 20", 20/1.0, 1
Rule.add "D", "1 for 15", 15/1.0, 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment