Решение на Трета задача от Йоан Карадимов

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

Към профила на Йоан Карадимов

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 16 успешни тест(а)
  • 3 неуспешни тест(а)

Код

require 'bigdecimal'
require 'bigdecimal/util'
class InventoryError < Exception
end
class Promotion
def self.create type, arg
return GetOneFreePromotion.new(arg) if type == :get_one_free
return PackagePromotion.new(arg) if type == :package
return ThresholdPromotion.new(arg) if type == :threshold
return NullPromotion.new if type == nil
end
def append_to_invoice(invoice, amount)
invoice.row(@name, '', amount)
end
end
class GetOneFreePromotion < Promotion
def initialize(every_n_th)
@name = " (buy #{every_n_th-1}, get 1 free)"
@every_n_th = every_n_th
end
def apply(price, count)
free_items_count = count / @every_n_th
price * free_items_count
end
end
class PackagePromotion < Promotion
def initialize(arg)
@package_size, @percentage = arg.to_a[0]
@name = " (get #{@percentage}% off for every #{@package_size})"
end
def apply(price, count)
packages_with_discount = count / @package_size * @package_size
price * packages_with_discount * @percentage.to_s.to_d / '100'.to_d
end
end
class ThresholdPromotion < Promotion
def initialize(arg)
@after_n_th, @percentage = arg.to_a[0]
#TODO 1st / 2nd/ 3rd suffixes
@name = " (#{@percentage}% off of every after the #{@after_n_th}th)"
end
def apply(price, count)
items_with_discount = count - @after_n_th
items_with_discount = 0 if items_with_discount < 0
price * items_with_discount * @percentage.to_s.to_d / '100'.to_d
end
end
class NullPromotion < Promotion
def append_to_invoice(invoice, amount)
end
def apply(price, count)
'0'.to_d
end
end
class Coupon
def self.create name, type, arg
return PercentDiscountCoupon.new(name, arg) if type == :percent
return AmountDiscountCoupon.new(name, arg) if type == :amount
return NullCoupon.new if type == nil
end
def append_to_invoice(invoice, total)
invoice.row @name, '', apply(total)
end
end
class PercentDiscountCoupon < Coupon
def initialize(name, percentage)
@factor = ('100'.to_d - percentage.to_s.to_d) / '100'.to_d
@name = "Coupon #{name} - #{percentage}% off"
end
def apply(total)
total * @factor
end
end
class AmountDiscountCoupon < Coupon
def initialize(name, discount)
@discount = discount.to_d
@name = "Coupon #{name} - #{discount} off"
end
def apply(total)
discounted_price = total - @discount
if discounted_price < 0
return '0'.to_d
else
return discounted_price
end
end
end
class NullCoupon < Coupon
def apply(total)
total
end
def append_to_invoice(invoice, total)
end
end
class InvoiceBuilder
# TODO: move special symbols to constants
def initialize
@result = ''
end
def separator
row('', '', '', '+', '-')
self
end
def row(name, qantity, price, separator = '|', blank = ' ')
@result << separator + blank
@result << name.ljust(40, blank) + blank
@result << qantity.rjust(5, blank) + blank
@result << separator + blank
@result << price.rjust(8, blank) + blank
@result << separator + "\n"
self
end
def append(item, arg)
item.append_to_invoice(self, arg)
self
end
def to_s
@result
end
end
class Cart
def initialize(inventory)
@inventory = inventory
@items_count = Hash.new 0
@coupon = NullCoupon.new
end
def add(item, count = 1)
raise InventoryError unless count_allowed?(@items_count[item] + count)
raise InventoryError unless @inventory.items.include? item
@items_count[item] += count
end
def use(coupon_name)
@coupon = @inventory.coupons[coupon_name]
end
def total
total_price = @items_count.keys.map { |item| price(item) + discount(item) }.inject(:+)
total_price = @coupon.apply total_price
total_price
end
def invoice
invoice = InvoiceBuilder.new.separator.row('Name', 'qty', 'price').separator
@items_count.each do |item, count|
invoice.row(item, count.to_s, format_dec(price(item)))
invoice.append(@inventory.promotions[item], format_dec(discount(item)))
end
total_amount = format_dec(total)
invoice.append(@coupon, total_amount).separator
invoice.row('TOTAL', '', total_amount).separator.to_s
end
private
MIN_ITEM_COUNT = 1
MAX_ITEM_COUNT = 99
def format_dec(decimal)
'%.2f' % decimal
end
def count_allowed?(count)
count >= MIN_ITEM_COUNT and count <= MAX_ITEM_COUNT
end
def price(item)
@inventory.items[item] * @items_count[item]
end
def discount(item)
- @inventory.promotions[item].apply(@inventory.items[item], @items_count[item])
end
end
class Inventory
def initialize
@items = {}
@promotions = {}
@coupons = {}
end
def register(item, price, promotion = {})
decimal_price = price.to_d
raise InventoryError unless price_allowed? decimal_price
raise InventoryError if @items.include? item
raise InventoryError if item.length > MAX_ITEM_NAME_LENGTH
@items[item] = decimal_price
type, arg = get_single_argument promotion
@promotions[item] = Promotion.create type, arg
end
def register_coupon(name, coupon_type)
type, arg = get_single_argument coupon_type
@coupons[name] = Coupon.create name, type, arg
end
def new_cart
Cart.new self
end
attr_reader :items, :coupons, :promotions
private
MAX_ALLOWED_PRICE = '999.99'.to_d
MIN_ALLOWED_PRICE = '0.01'.to_d
MAX_ITEM_NAME_LENGTH = 40
def price_allowed?(price)
price >= MIN_ALLOWED_PRICE and price <= MAX_ALLOWED_PRICE
end
def get_single_argument(args)
if args.length == 0
return nil, nil
elsif args.length > 1
raise InventoryError # TODO - only one named arguemnt can be applied
else
return args.to_a[0]
end
end
end

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

..............F..FF

Failures:

  1) Inventory with a '% off' coupon shows the discount in the invoice
     Failure/Error: cart.invoice.should eq <<INVOICE
       
       expected: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                   10 |    10.00 |\n| Coupon TEA-TIME - 20% off                      |    -2.00 |\n+------------------------------------------------+----------+\n| TOTAL                                          |     8.00 |\n+------------------------------------------------+----------+\n"
            got: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                   10 |    10.00 |\n| Coupon TEA-TIME - 20% off                      |          |\n+------------------------------------------------+----------+\n| TOTAL                                          |     8.00 |\n+------------------------------------------------+----------+\n"
       
       (compared using ==)
       
       Diff:
       @@ -2,7 +2,7 @@
        | Name                                       qty |    price |
        +------------------------------------------------+----------+
        | Green Tea                                   10 |    10.00 |
       -| Coupon TEA-TIME - 20% off                      |    -2.00 |
       +| Coupon TEA-TIME - 20% off                      |          |
        +------------------------------------------------+----------+
        | TOTAL                                          |     8.00 |
        +------------------------------------------------+----------+
     # /tmp/d20111115-5847-2byysc/spec.rb:240: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)>'

  2) Inventory with an 'amount off' coupon shows the discount in the invoice
     Failure/Error: cart.invoice.should eq <<INVOICE
     NoMethodError:
       undefined method `-' for "0.00":String
     # /tmp/d20111115-5847-2byysc/solution.rb:100:in `apply'
     # /tmp/d20111115-5847-2byysc/solution.rb:78:in `append_to_invoice'
     # /tmp/d20111115-5847-2byysc/solution.rb:143:in `append'
     # /tmp/d20111115-5847-2byysc/solution.rb:183:in `invoice'
     # /tmp/d20111115-5847-2byysc/spec.rb:281: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)>'

  3) Inventory with multiple discounts can print an invoice
     Failure/Error: cart.invoice.should eq <<INVOICE
       
       expected: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                    8 |    22.32 |\n|   (buy 1, get 1 free)                          |   -11.16 |\n| Black Coffee                                 5 |    14.95 |\n|   (get 20% off for every 2)                    |    -2.39 |\n| Milk                                         5 |     8.95 |\n|   (30% off of every after the 3rd)             |    -1.07 |\n| Cereal                                       3 |     7.47 |\n| Coupon BREAKFAST - 10% off                     |    -3.91 |\n+------------------------------------------------+----------+\n| TOTAL                                          |    35.16 |\n+------------------------------------------------+----------+\n"
            got: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                    8 |    22.32 |\n|   (buy 1, get 1 free)                          |   -11.16 |\n| Black Coffee                                 5 |    14.95 |\n|   (get 20% off for every 2)                    |    -2.39 |\n| Milk                                         5 |     8.95 |\n|   (30% off of every after the 3th)             |    -1.07 |\n| Cereal                                       3 |     7.47 |\n| Coupon BREAKFAST - 10% off                     |          |\n+------------------------------------------------+----------+\n| TOTAL                                          |    35.16 |\n+------------------------------------------------+----------+\n"
       
       (compared using ==)
       
       Diff:
       
       @@ -6,9 +6,9 @@
        | Black Coffee                                 5 |    14.95 |
        |   (get 20% off for every 2)                    |    -2.39 |
        | Milk                                         5 |     8.95 |
       -|   (30% off of every after the 3rd)             |    -1.07 |
       +|   (30% off of every after the 3th)             |    -1.07 |
        | Cereal                                       3 |     7.47 |
       -| Coupon BREAKFAST - 10% off                     |    -3.91 |
       +| Coupon BREAKFAST - 10% off                     |          |
        +------------------------------------------------+----------+
        | TOTAL                                          |    35.16 |
        +------------------------------------------------+----------+
     # /tmp/d20111115-5847-2byysc/spec.rb:309: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.57166 seconds
19 examples, 3 failures

Failed examples:

rspec /tmp/d20111115-5847-2byysc/spec.rb:233 # Inventory with a '% off' coupon shows the discount in the invoice
rspec /tmp/d20111115-5847-2byysc/spec.rb:274 # Inventory with an 'amount off' coupon shows the discount in the invoice
rspec /tmp/d20111115-5847-2byysc/spec.rb:295 # Inventory with multiple discounts can print an invoice

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

Йоан обнови решението на 07.11.2011 16:23 (преди около 13 години)

+require 'bigdecimal'
+require 'bigdecimal/util'
+
+class InventoryError < Exception
+end
+
+
+class Promotion
+ def self.create type, arg
+ return GetOneFreePromotion.new(arg) if type == :get_one_free
+ return PackagePromotion.new(arg) if type == :package
+ return ThresholdPromotion.new(arg) if type == :threshold
+ return NullPromotion.new if type == nil
+ end
+
+ def append_to_invoice(invoice, amount)
+ invoice.row(@name, '', amount)
+ end
+end
+
+class GetOneFreePromotion < Promotion
+ def initialize(every_n_th)
+ @name = " (buy #{every_n_th-1}, get 1 free)"
+ @every_n_th = every_n_th
+ end
+
+ def apply(price, count)
+ free_items_count = count / @every_n_th
+ price * free_items_count
+ end
+end
+
+class PackagePromotion < Promotion
+ def initialize(arg)
+ @package_size, @percentage = arg.to_a[0]
+ @name = " (get #{@percentage}% off for every #{@package_size})"
+ end
+
+ def apply(price, count)
+ packages_with_discount = count / @package_size * @package_size
+ price * packages_with_discount * @percentage.to_s.to_d / '100'.to_d
+ end
+end
+
+class ThresholdPromotion < Promotion
+ def initialize(arg)
+ @after_n_th, @percentage = arg.to_a[0]
+
+ #TODO 1st / 2nd/ 3rd suffixes
+ @name = " (#{@percentage}% off of every after the #{@after_n_th}th)"
+ end
+
+ def apply(price, count)
+ items_with_discount = count - @after_n_th
+ items_with_discount = 0 if items_with_discount < 0
+ price * items_with_discount * @percentage.to_s.to_d / '100'.to_d
+ end
+end
+
+class NullPromotion < Promotion
+ def append_to_invoice(invoice, amount)
+ end
+
+ def apply(price, count)
+ '0'.to_d
+ end
+end
+
+class Coupon
+
+ def self.create name, type, arg
+ return PercentDiscountCoupon.new(name, arg) if type == :percent
+ return AmountDiscountCoupon.new(name, arg) if type == :amount
+ return NullCoupon.new if type == nil
+ end
+
+ def append_to_invoice(invoice, total)
+ invoice.row @name, '', apply(total)
+ end
+end
+
+class PercentDiscountCoupon < Coupon
+ def initialize(name, percentage)
+ @factor = ('100'.to_d - percentage.to_s.to_d) / '100'.to_d
+ @name = "Coupon #{name} - #{percentage}% off"
+ end
+
+ def apply(total)
+ total * @factor
+ end
+end
+
+class AmountDiscountCoupon < Coupon
+ def initialize(name, discount)
+ @discount = discount.to_d
+ @name = "Coupon #{name} - #{discount} off"
+ end
+
+ def apply(total)
+ discounted_price = total - @discount
+ if discounted_price < 0
+ return '0'.to_d
+ else
+ return discounted_price
+ end
+ end
+end
+
+class NullCoupon < Coupon
+
+ def apply(total)
+ total
+ end
+
+ def append_to_invoice(invoice, total)
+ end
+end
+
+class InvoiceBuilder
+
+ # TODO: move special symbols to constants
+
+ def initialize
+ @result = ''
+ end
+
+ def separator
+ row('', '', '', '+', '-')
+ self
+ end
+
+ def row(name, qantity, price, separator = '|', blank = ' ')
+ @result << separator + blank
+ @result << name.ljust(40, blank) + blank
+ @result << qantity.rjust(5, blank) + blank
+ @result << separator + blank
+ @result << price.rjust(8, blank) + blank
+ @result << separator + "\n"
+ self
+ end
+
+ def append(item, arg)
+ item.append_to_invoice(self, arg)
+ self
+ end
+
+ def to_s
+ @result
+ end
+
+end
+
+class Cart
+ def initialize(inventory)
+ @inventory = inventory
+ @items_count = Hash.new 0
+ @coupon = NullCoupon.new
+ end
+
+ def add(item, count = 1)
+ raise InventoryError unless count_allowed?(@items_count[item] + count)
+ raise InventoryError unless @inventory.items.include? item
+ @items_count[item] += count
+ end
+
+ def use(coupon_name)
+ @coupon = @inventory.coupons[coupon_name]
+ end
+
+ def total
+ total_price = @items_count.keys.map { |item| price(item) + discount(item) }.inject(:+)
+ total_price = @coupon.apply total_price
+ total_price
+ end
+
+ def invoice
+ invoice = InvoiceBuilder.new.separator.row('Name', 'qty', 'price').separator
+ @items_count.each do |item, count|
+ invoice.row(item, count.to_s, format_dec(price(item)))
+ invoice.append(@inventory.promotions[item], format_dec(discount(item)))
+ end
+ total_amount = format_dec(total)
+ invoice.append(@coupon, total_amount).separator
+ invoice.row('TOTAL', '', total_amount).separator.to_s
+ end
+
+ private
+
+ MIN_ITEM_COUNT = 1
+ MAX_ITEM_COUNT = 99
+
+ def format_dec(decimal)
+ '%.2f' % decimal
+ end
+
+ def count_allowed?(count)
+ count >= MIN_ITEM_COUNT and count <= MAX_ITEM_COUNT
+ end
+
+ def price(item)
+ @inventory.items[item] * @items_count[item]
+ end
+
+ def discount(item)
+ - @inventory.promotions[item].apply(@inventory.items[item], @items_count[item])
+ end
+end
+
+class Inventory
+ def initialize
+ @items = {}
+ @promotions = {}
+ @coupons = {}
+ end
+
+ def register(item, price, promotion = {})
+ decimal_price = price.to_d
+ raise InventoryError unless price_allowed? decimal_price
+ raise InventoryError if @items.include? item
+ raise InventoryError if item.length > MAX_ITEM_NAME_LENGTH
+ @items[item] = decimal_price
+ type, arg = get_single_argument promotion
+ @promotions[item] = Promotion.create type, arg
+ end
+
+ def register_coupon(name, coupon_type)
+ type, arg = get_single_argument coupon_type
+ @coupons[name] = Coupon.create name, type, arg
+ end
+
+ def new_cart
+ Cart.new self
+ end
+
+ attr_reader :items, :coupons, :promotions
+
+ private
+
+ MAX_ALLOWED_PRICE = '999.99'.to_d
+ MIN_ALLOWED_PRICE = '0.01'.to_d
+ MAX_ITEM_NAME_LENGTH = 40
+
+ def price_allowed?(price)
+ price >= MIN_ALLOWED_PRICE and price <= MAX_ALLOWED_PRICE
+ end
+
+ def get_single_argument(args)
+ if args.length == 0
+ return nil, nil
+ elsif args.length > 1
+ raise InventoryError # TODO - only one named arguemnt can be applied
+ else
+ return args.to_a[0]
+ end
+ end
+
+end