Камен обнови решението на 05.11.2011 22:16 (преди около 13 години)
+require 'bigdecimal'
+require 'bigdecimal/util' # добавя String#to_d
+
+class Cart
+ def initialize(items, copons)
+ @cart_items = Hash.new(0)
+ @cart_coupons = {}
+ @items = items
+ @copons = copons
+ @out = Output.new
+ end
+
+ def add(item_name, count = 1)
+ if ( @items[item_name] == nil or count <= 0 or count > 99 )
+ raise "Invalid parameters passed."
+ end
+
+ @cart_items[item_name] += count
+ end
+
+ def use(coupon_name)
+ if ( @copons[coupon_name] == nil )
+ raise "Invalid parameters passed."
+ end
+
+ @cart_coupons[coupon_name] = @copons[coupon_name]
+ end
+
+ def total
+ # Compute all coupon discounts and sum it at the end
+ # Also take care of the possibility for negative total
+ total = self.total_without_coupons
+ discounts = [0] + @cart_coupons.map { |name,cop| process_coupon(name, total, total)[1] }
+ return [total + discounts.inject(:+),0].max()
+ end
+
+ def invoice
+ invoice = @out.write_line + @out.write("Name", "qty", "price") + @out.write_line
+ invoice += invoice_items + invoice_coupons
+ invoice += @out.write_line + @out.write( "TOTAL", "", total.to_s ) + @out.write_line
+ return invoice
+ end
+
+ def total_without_coupons
+ # Merge items and promotions into "sums" array and sum it at the end
+ sums = [0] + @cart_items.map { |name,count| count*@items[name].price.to_d() }
+ sums += @cart_items.map do |name,count|
+ @items[name].promotion.discount(@items[name].price.to_d(),count)
+ end
+ return sums.inject(:+)
+ end
+
+ def invoice_items
+ invoice = ""
+ @cart_items.each do |name,count|
+ promotion = @items[name].promotion
+ price = @items[name].price.to_d()
+ invoice += @out.write( name, count.to_s(), (count*price).to_s() )
+ invoice += @out.write( promotion.description(), "", promotion.discount(price,count))
+ end
+ return invoice
+ end
+
+ def invoice_coupons
+ invoice = ""
+ limit = total_without_coupons
+ @cart_coupons.each do |name,details|
+ coupon_info = process_coupon(name, total_without_coupons, limit )
+ invoice += @out.write( coupon_info[0], "", coupon_info[1] )
+ limit -= coupon_info[1]
+ end
+ return invoice
+ end
+
+ # Returns an array containing the coupon description and the discount amount
+ # In case of an amount coupon we do not go over the limit
+ def process_coupon( coupon_name, price, limit )
+ coupon = @copons[coupon_name]
+ discount = coupon.values.first
+
+ if ( coupon.keys.first == :percent )
+ return ["Coupon #{coupon_name} - #{discount}% off",-price*discount/100.0]
+ end
+ if ( coupon.keys.first == :amount )
+ amount = [discount.to_d,limit].min()
+ return ["Coupon #{coupon_name} - #{'%0.2f' % discount} off",-amount ]
+ end
+ end
+
+end
+
+class Inventory
+ def initialize()
+ @items = {}
+ @coupons = {}
+ @carts = []
+ end
+
+ def register(item_name, item_price, promotion = {} )
+ if ( item_name.length > 40 or item_price.to_d < 0.01 or item_price.to_d > 999.99 )
+ raise "Invalid parameters passed."
+ end
+
+ if @items[item_name] != nil
+ raise "Invalid parameters passed."
+ end
+
+ @items[item_name] = Item.new( item_price, Promotion.new(promotion) )
+ end
+
+ def register_coupon(coupon_name, coupon)
+ @coupons[coupon_name] = coupon
+ end
+
+ def new_cart
+ cart = Cart.new(@items,@coupons)
+ @carts.push(cart)
+ return cart
+ end
+
+end
+
+class Item
+ def initialize(price, promotion)
+ @price = price
+ @promotion = promotion
+ end
+
+ attr_accessor :price,:promotion
+end
+
+class Promotion
+ def initialize(promotion)
+ @name = promotion.keys.first
+ @details = promotion.values.first
+ end
+
+ # Calculate dicount based on the type of promotion
+ def discount(item_price, item_count)
+ if ( name == :get_one_free ) then return -(item_count / details)*item_price
+ elsif ( name == :package )
+ packages = (item_count / details.keys.first)
+ return -packages*details.keys.first*(details.values.first/100.0)*item_price
+ elsif ( name == :threshold )
+ packages = [item_count - details.keys.first,0].max
+ return -packages*(details.values.first/100.0)*item_price
+ end
+
+ return 0
+ end
+
+ # Get description based on the type of promotion
+ def description
+ if ( name == :get_one_free )
+ return " (buy #{details-1}, get 1 free)"
+ elsif ( name == :package )
+ return " (get #{details.values.first}% off for every #{details.keys.first})"
+ elsif ( name == :threshold )
+ ordinal = self.ordinal(details.keys.first)
+ return " (#{details.values.first}% off of every after the #{ordinal})"
+ end
+
+ return ""
+ end
+
+ # Add ordinal suffix to the number and return it
+ def ordinal(number)
+ # See http://en.wikipedia.org/wiki/English_numerals
+ table = ["th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"]
+
+ number_as_string = number.to_s()
+ units = number_as_string[number_as_string.length-1].to_i()
+ tens = 0
+ if ( number_as_string.length >= 2 )
+ tens = number_as_string[number_as_string.length-2] .to_i()
+ end
+
+ if ( tens == 1) then return number_as_string+"th"
+ else return number_as_string+table[units] end
+
+ end
+
+ attr_accessor :name,:details
+end
+
+class Output
+ def write(name, count, price )
+ # Items with empty promotions will try to write to the output empty lines
+ if ( name == "" ) then return "" end
+
+ # Write Name and Quantity columns
+ number_of_whitespaces = 48 - name.length - 2 - count.length
+ invoice = "| #{name}" + " " * number_of_whitespaces + count + " |"
+
+ # Price could be a String - we need to catch exceptions
+ begin
+ price_string = "%0.2f" % price
+ rescue
+ price_string = price
+ end
+
+ # Write Price column
+ number_of_whitespaces = 10 - price_string.length - 1
+ invoice += " "*number_of_whitespaces + price_string + " |\n"
+
+ return invoice
+ end
+
+ def write_line
+ return "+------------------------------------------------+----------+\n"
+ end
+
+end