Решение на Трета задача от Евгени Кунев

Обратно към всички решения

Към профила на Евгени Кунев

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 18 успешни тест(а)
  • 1 неуспешни тест(а)

Код

require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/integer/inflections'
class Inventory
attr_reader :products, :coupons
def initialize
@products = {}
@coupons = {}
end
def register(name, price, promotion={})
price = price.to_d
raise "Duplicate #{name}" if @products.include? name
raise "Negative price for #{name}" if price <= 0
raise "#{name} - to long name" if name.length > 40
raise "#{name} is too expensive" if price > 999.99
raise "#{name} has more than one promotion" if promotion.length > 1
@products[name] = Product.new(name, price, promotion)
end
def register_coupon(name, coupon_data)
raise "Coupon #{name} already registered" if @coupons.include? name
raise "Bad data for coupon #{name}" if coupon_data.length != 1
@coupons[name] = Coupon.new(*coupon_data.to_a.flatten)
end
def new_cart
ShoppingCart.new self
end
def cost_of(count, product)
@products[product].cost_for count
end
def discount_from_coupon(coupon, price)
@coupons[coupon].discount_from_coupon(price)
end
end
class ShoppingCart
attr_reader :products, :coupon, :inventory
def initialize(inventory)
@inventory = inventory
@products = {}
@products.default = 0
end
def add(name, count=1)
raise "No #{name} in inventory" if not @inventory.products.include? name
raise "Negative count for #{name}" if @products[name] + count <= 0
raise "Too much #{name}" if @products[name] + count > 99
@products[name] += count
end
def use(coupon)
raise "Missing coupon #{coupon} " if not @inventory.coupons.include? coupon
@coupon = coupon
end
def total
@coupon ? price - @inventory.discount_from_coupon(@coupon, price) : price
end
def price
@products.map{|name, count| @inventory.cost_of count, name }.inject(&:+)
end
def invoice
InvoicePrinter.new(self).get_invoice
end
end
class Coupon
attr_reader :type, :count
def initialize(type, count)
@type = type
@count = count.to_s.to_d
end
def total_after_discount(total)
total - discount_from_coupon(total)
end
def discount_from_coupon(price)
case @type
when :percent
price*@count/100
when :amount
@count < price ? @count : price
end
end
end
class Product
attr_reader :name, :price, :promotion_type,
:promotion_attr, :group_size,
:discount_percent, :threshold
alias eq? ==
def initialize(name, price, promotion)
@name, @price = name, price
@promotion_type, @promotion_attr = *promotion.to_a.flatten
extract_promotion_params
end
def extract_promotion_params
case @promotion_type
when :package
@group_size, @discount_percent = *@promotion_attr.to_a.flatten
when :threshold
@threshold, @discount_percent = *@promotion_attr.to_a.flatten
end
end
def cost_for(count)
base_price = count*@price
discount = discount_for count
@price*count - discount
end
def discount_for(count)
case @promotion_type
when :get_one_free
get_one_free_discount count
when :package
package_discount count
when :threshold
threshold_discount count
else
0
end
end
def get_one_free_discount(count)
@price*(count/@promotion_attr)
end
def package_discount(count)
groups = count/@group_size
groups*@group_size*@price*@discount_percent/100
end
def threshold_discount(count)
return 0 if count <= @threshold
(count - @threshold)*@price*@discount_percent/100
end
end
class InvoicePrinter
HEADER = <<HEADER
+#{'-'*48}+#{'-'*10}+\n| Name#{' '*39}qty | price |\n+#{'-'*48}+#{'-'*10}+
HEADER
TOTAL = <<TOTAL
+#{'-'*48}+#{'-'*10}+\n| TOTAL#{' '*41} | %5.2f |\n+#{'-'*48}+#{'-'*10}+
TOTAL
def initialize(cart)
@cart = cart
end
def get_invoice
all_prods = @cart.inventory.products
strings = @cart.products.map { |name, count| product_invoice(all_prods[name], count) }
strings << (coupon_invoice(@cart.coupon) or '')
strings << TOTAL % [@cart.total]
strings.inject(HEADER) { |str1, str2| str1 + str2 }
end
def product_invoice(product, count)
product_invoice_line(product, count) +
(promotion_invoice_line(product, count) or '')
end
private
def product_invoice_line(product, count)
"| %-44s%2s |%9.2f |\n" % [product.name, count, count*product.price]
end
def promotion_invoice_line(product, count)
if product.promotion_type == :get_one_free
one_free_promotion_line product, count
elsif product.promotion_type == :package
package_promotion_line product, count
elsif product.promotion_type == :threshold
threshold_promotion_line product, count
end
end
def one_free_promotion_line(product, count)
"| %-45s" %
("(buy %d, get 1 free)" % [product.promotion_attr-1]) +
"|%9.2f |\n" % [-product.discount_for(count)]
end
def package_promotion_line(product, count)
"| %-45s" % ("(get %s%% off for every %s)" %
[product.discount_percent, product.group_size]) +
"|%9.2f |\n" % [-product.discount_for(count)]
end
def threshold_promotion_line(product, count)
"| %-45s" % "(%s%% off of every after the %s)" %
[product.discount_percent, product.threshold.ordinalize] +
"|%9.2f |\n" % [-product.discount_for(count)]
end
def coupon_invoice(coupon)
return if not coupon
discount = @cart.inventory.coupons[coupon].discount_from_coupon(@cart.price)
"| Coupon #{coupon} - #{coupon_description coupon} off".ljust(49) +
"| %8.2f |\n" % -discount
end
def coupon_description(coupon_name)
coupon = @cart.inventory.coupons[coupon_name]
case coupon.type
when :percent
"%d%%" % coupon.count
when :amount
"%.2f" % coupon.count
end
end
end

Лог от изпълнението

...........F.......

Failures:

  1) Inventory with a '% off of every item after the nth' promotion shows the discount in the ivnoice
     Failure/Error: cart.invoice.should eq <<INVOICE
       
       expected: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                   12 |    12.00 |\n|   (10% off of every after the 10th)            |    -0.20 |\n| Red Tea                                     20 |    40.00 |\n|   (20% off of every after the 15th)            |    -2.00 |\n+------------------------------------------------+----------+\n| TOTAL                                          |    49.80 |\n+------------------------------------------------+----------+\n"
            got: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                   12 |    12.00 |\n|   (10% off of every after the 10th)             |    -0.20 |\n| Red Tea                                     20 |    40.00 |\n|   (20% off of every after the 15th)             |    -2.00 |\n+------------------------------------------------+----------+\n| TOTAL                                          |    49.80 |\n+------------------------------------------------+----------+\n"
       
       (compared using ==)
       
       Diff:
       
       @@ -2,9 +2,9 @@
        | Name                                       qty |    price |
        +------------------------------------------------+----------+
        | Green Tea                                   12 |    12.00 |
       -|   (10% off of every after the 10th)            |    -0.20 |
       +|   (10% off of every after the 10th)             |    -0.20 |
        | Red Tea                                     20 |    40.00 |
       -|   (20% off of every after the 15th)            |    -2.00 |
       +|   (20% off of every after the 15th)             |    -2.00 |
        +------------------------------------------------+----------+
        | TOTAL                                          |    49.80 |
        +------------------------------------------------+----------+
     # /tmp/d20111115-5847-1rmwk1c/spec.rb:197:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.66046 seconds
19 examples, 1 failure

Failed examples:

rspec /tmp/d20111115-5847-1rmwk1c/spec.rb:190 # Inventory with a '% off of every item after the nth' promotion shows the discount in the ivnoice

История (1 версия и 0 коментара)

Евгени обнови решението на 06.11.2011 22:46 (преди около 13 години)

+require 'bigdecimal'
+require 'bigdecimal/util'
+require 'active_support/core_ext/integer/inflections'
+
+class Inventory
+ attr_reader :products, :coupons
+ def initialize
+ @products = {}
+ @coupons = {}
+ end
+
+ def register(name, price, promotion={})
+ price = price.to_d
+ raise "Duplicate #{name}" if @products.include? name
+ raise "Negative price for #{name}" if price <= 0
+ raise "#{name} - to long name" if name.length > 40
+ raise "#{name} is too expensive" if price > 999.99
+ raise "#{name} has more than one promotion" if promotion.length > 1
+ @products[name] = Product.new(name, price, promotion)
+ end
+
+ def register_coupon(name, coupon_data)
+ raise "Coupon #{name} already registered" if @coupons.include? name
+ raise "Bad data for coupon #{name}" if coupon_data.length != 1
+ @coupons[name] = Coupon.new(*coupon_data.to_a.flatten)
+ end
+
+ def new_cart
+ ShoppingCart.new self
+ end
+
+ def cost_of(count, product)
+ @products[product].cost_for count
+ end
+
+ def discount_from_coupon(coupon, price)
+ @coupons[coupon].discount_from_coupon(price)
+ end
+end
+
+
+class ShoppingCart
+ attr_reader :products, :coupon, :inventory
+
+ def initialize(inventory)
+ @inventory = inventory
+ @products = {}
+ @products.default = 0
+ end
+
+ def add(name, count=1)
+ raise "No #{name} in inventory" if not @inventory.products.include? name
+ raise "Negative count for #{name}" if @products[name] + count <= 0
+ raise "Too much #{name}" if @products[name] + count > 99
+ @products[name] += count
+ end
+
+ def use(coupon)
+ raise "Missing coupon #{coupon} " if not @inventory.coupons.include? coupon
+ @coupon = coupon
+ end
+
+ def total
+ @coupon ? price - @inventory.discount_from_coupon(@coupon, price) : price
+ end
+
+ def price
+ @products.map{|name, count| @inventory.cost_of count, name }.inject(&:+)
+ end
+
+ def invoice
+ InvoicePrinter.new(self).get_invoice
+ end
+end
+
+
+class Coupon
+ attr_reader :type, :count
+ def initialize(type, count)
+ @type = type
+ @count = count.to_s.to_d
+ end
+
+ def total_after_discount(total)
+ total - discount_from_coupon(total)
+ end
+
+ def discount_from_coupon(price)
+ case @type
+ when :percent
+ price*@count/100
+ when :amount
+ @count < price ? @count : price
+ end
+ end
+end
+class Product
+ attr_reader :name, :price, :promotion_type,
+ :promotion_attr, :group_size,
+ :discount_percent, :threshold
+ alias eq? ==
+
+ def initialize(name, price, promotion)
+ @name, @price = name, price
+ @promotion_type, @promotion_attr = *promotion.to_a.flatten
+ extract_promotion_params
+ end
+
+ def extract_promotion_params
+ case @promotion_type
+ when :package
+ @group_size, @discount_percent = *@promotion_attr.to_a.flatten
+ when :threshold
+ @threshold, @discount_percent = *@promotion_attr.to_a.flatten
+ end
+ end
+
+ def cost_for(count)
+ base_price = count*@price
+ discount = discount_for count
+ @price*count - discount
+ end
+
+ def discount_for(count)
+ case @promotion_type
+ when :get_one_free
+ get_one_free_discount count
+ when :package
+ package_discount count
+ when :threshold
+ threshold_discount count
+ else
+ 0
+ end
+ end
+
+ def get_one_free_discount(count)
+ @price*(count/@promotion_attr)
+ end
+
+ def package_discount(count)
+ groups = count/@group_size
+ groups*@group_size*@price*@discount_percent/100
+ end
+
+ def threshold_discount(count)
+ return 0 if count <= @threshold
+ (count - @threshold)*@price*@discount_percent/100
+ end
+end
+
+
+class InvoicePrinter
+ HEADER = <<HEADER
++#{'-'*48}+#{'-'*10}+\n| Name#{' '*39}qty | price |\n+#{'-'*48}+#{'-'*10}+
+HEADER
+ TOTAL = <<TOTAL
++#{'-'*48}+#{'-'*10}+\n| TOTAL#{' '*41} | %5.2f |\n+#{'-'*48}+#{'-'*10}+
+TOTAL
+
+ def initialize(cart)
+ @cart = cart
+ end
+
+ def get_invoice
+ all_prods = @cart.inventory.products
+ strings = @cart.products.map { |name, count| product_invoice(all_prods[name], count) }
+ strings << (coupon_invoice(@cart.coupon) or '')
+ strings << TOTAL % [@cart.total]
+ strings.inject(HEADER) { |str1, str2| str1 + str2 }
+ end
+
+ def product_invoice(product, count)
+ product_invoice_line(product, count) +
+ (promotion_invoice_line(product, count) or '')
+ end
+
+ private
+ def product_invoice_line(product, count)
+ "| %-44s%2s |%9.2f |\n" % [product.name, count, count*product.price]
+ end
+
+ def promotion_invoice_line(product, count)
+ if product.promotion_type == :get_one_free
+ one_free_promotion_line product, count
+ elsif product.promotion_type == :package
+ package_promotion_line product, count
+ elsif product.promotion_type == :threshold
+ threshold_promotion_line product, count
+ end
+ end
+
+ def one_free_promotion_line(product, count)
+ "| %-45s" %
+ ("(buy %d, get 1 free)" % [product.promotion_attr-1]) +
+ "|%9.2f |\n" % [-product.discount_for(count)]
+ end
+
+ def package_promotion_line(product, count)
+ "| %-45s" % ("(get %s%% off for every %s)" %
+ [product.discount_percent, product.group_size]) +
+ "|%9.2f |\n" % [-product.discount_for(count)]
+ end
+
+ def threshold_promotion_line(product, count)
+ "| %-45s" % "(%s%% off of every after the %s)" %
+ [product.discount_percent, product.threshold.ordinalize] +
+ "|%9.2f |\n" % [-product.discount_for(count)]
+ end
+
+ def coupon_invoice(coupon)
+ return if not coupon
+ discount = @cart.inventory.coupons[coupon].discount_from_coupon(@cart.price)
+ "| Coupon #{coupon} - #{coupon_description coupon} off".ljust(49) +
+ "| %8.2f |\n" % -discount
+ end
+
+ def coupon_description(coupon_name)
+ coupon = @cart.inventory.coupons[coupon_name]
+ case coupon.type
+ when :percent
+ "%d%%" % coupon.count
+ when :amount
+ "%.2f" % coupon.count
+ end
+ end
+end