Решение на Трета задача от Мартин Асенов

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

Към профила на Мартин Асенов

Резултати

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

Код

require 'bigdecimal'
require 'bigdecimal/util'
#TODO refactor/cleanup + use modules instead of utils if there's time
class Product
attr_reader :name, :price, :discount
def initialize(name, price, discount)
validate_name(name)
validate_price(price)
@name = name
@price = price.to_d
@discount = DiscountUtil.get_discount_instance(discount)
end
#returns the total amount of money the user should pay for the items bought
def calculate_product_total(items_bought)
result = items_bought * @price
if @discount
result -= @discount.apply(self, items_bought)
end
result
end
private
def validate_name(product_name)
if product_name.length > 40
raise "Product name is too long"
end
end
def validate_price(product_price)
parsed_price = product_price.to_d
if parsed_price < 0.01 or parsed_price > 999.99
raise "Incorrect product price"
end
end
end
class DiscountUtil
def self.get_discount_instance(discount_hash)
if discount_hash[:get_one_free]
OneFreeDiscount.new(discount_hash[:get_one_free])
elsif discount_hash[:package]
PackageDiscount.new(discount_hash[:package])
elsif discount_hash[:threshold]
AmountDiscount.new(discount_hash[:threshold])
else
nil
end
end
end
class BaseDiscount
def apply(product, items_bought)
raise "This should be overriden"
end
end
class OneFreeDiscount < BaseDiscount
attr_reader :item_at, :free_items
def initialize(item_at_is_free)
@item_at = item_at_is_free
end
def apply(product, items_bought)
@free_items = items_bought / @item_at
res = @free_items * product.price
while @free_items / @item_at > 0
@free_items = @free_items / @item_at
end
res
end
end
class PackageDiscount < BaseDiscount
attr_reader :percent, :discount_for_every
def initialize(package_discount_hash)
package_discount_hash.each do |key, value|
@discount_for_every = key
@percent = value
end
end
#returns the amount of the discount
def apply(product, items_bought)
times_discount_applied = items_bought / @discount_for_every
percent = BigDecimal((@percent/100.to_f).to_s)
discounted_items = BigDecimal((times_discount_applied * @discount_for_every).to_s)
percent * discounted_items * product.price
end
end
class AmountDiscount < BaseDiscount
attr_reader :threshold, :percent
def initialize(amount_discount_hash)
amount_discount_hash.each do |key, value|
@threshold = key
@percent = value
end
end
#returns the amount of the discount
def apply(product, items_bought)
if items_bought < @threshold
0
else
percent = BigDecimal((@percent/100.to_f).to_s)
discounted_items = BigDecimal((items_bought - @threshold).to_s)
percent * discounted_items * product.price
end
end
end
class CouponUtil
def self.get_coupon_instance(coupon_name, discount_hash)
if discount_hash[:percent]
PercentDiscountCoupon.new(coupon_name,discount_hash[:percent])
else
FixedAmountCoupon.new(coupon_name,discount_hash[:amount])
end
end
end
class BaseCoupon
def use(cart_items)
raise "this must be overriden"
end
end
class PercentDiscountCoupon < BaseCoupon
attr_reader :name, :percent
def initialize(coupon_name, percent)
@name = coupon_name
@percent = percent
end
def use(total)
percent = BigDecimal(((100 - @percent)/100.to_f).to_s)
percent * total
end
end
class FixedAmountCoupon < BaseCoupon
attr_reader :name, :amount
def initialize(coupon_name, discount_amount)
@name = coupon_name
@amount= discount_amount
end
def use(total)
total - @amount
end
end
class Inventory
attr_reader :products, :coupons
def initialize
@products = {}
@coupons = {}
end
def register(product_name, product_price, product_discount={})
if not @products[product_name]
@products[product_name] = Product.new(product_name, product_price, product_discount)
end
end
def register_coupon(coupon_name, discount_hash)
@coupons[coupon_name] = CouponUtil.get_coupon_instance(coupon_name, discount_hash)
end
def new_cart
Cart.new self
end
end
class FormatUtil
def self.format_number(number)
sprintf("%.2f", number)
end
def self.format_c(number)
if number == 1
suffix = "st"
elsif number == 2
suffix = "nd"
elsif number == 3
suffix = "rd"
else
suffix = "th"
end
number.to_s + suffix
end
end
class DiscountUtil
NAME_QTY_FIELD_LENGTH = 48
PRICE_FIELD_LENGTH = 10
def self.one_free_discount_to_s(product, items_bought)
items_to_buy = product.discount.item_at - 1
res = "| (buy #{items_to_buy}, get #{product.discount.free_items} free)"
res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
discount = FormatUtil.format_number(-product.discount.apply(product, items_bought))
res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount + " |\n"
res
end
def self.package_discount_to_s(product,qty)
percent = product.discount.percent
for_every = product.discount.discount_for_every
res = "| (get #{percent}% off for every #{for_every})"
res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
discount = FormatUtil.format_number(-product.discount.apply(product,qty))
res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount +" |\n"
res
end
def self.amount_discount_to_s(product,qty)
percent = product.discount.percent
threshold = product.discount.threshold
res = "| (#{percent}% off of every after the #{FormatUtil.format_c(threshold)})"
res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
discount = FormatUtil.format_number(-product.discount.apply(product,qty))
res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount + " |\n"
res
end
end
class InvoiceUtil
NAME_QTY_FIELD_LENGTH = 48
PRICE_FIELD_LENGTH = 10
def self.product_to_s(product, qty)
res = "| " + product.name + (" " * get_name_blank_spaces(product.name, qty))
res += qty.to_s+" |"
res += " " * get_price_blank_spaces(product.price * qty)
res += FormatUtil.format_number((product.price * qty).to_f) + " |\n"
res += attach_product_promotions(product, qty)
res
end
def self.total_to_s(total)
res = "| TOTAL |"
formatted_total = FormatUtil.format_number(total.to_f)
res += " " * get_price_blank_spaces(total) + formatted_total + " |\n"
res
end
def self.coupon_to_s(coupon, discount)
res = "| Coupon " + coupon.name + " - "
if coupon.kind_of? FixedAmountCoupon
res += FormatUtil.format_number(coupon.amount.to_f) + " off"
else
res += coupon.percent.to_s + "% off"
end
blank_spaces = NAME_QTY_FIELD_LENGTH - res.length + 1
res += " " * blank_spaces + "|"
res += " " * get_price_blank_spaces(discount)
res += FormatUtil.format_number(discount.to_f) + " |\n"
res
end
private
def self.get_price_blank_spaces(price)
PRICE_FIELD_LENGTH - FormatUtil.format_number(price.to_f).length - 1
end
def self.get_name_blank_spaces(name, qty)
NAME_QTY_FIELD_LENGTH - name.length - qty.to_s.length - 2
end
def self.attach_product_promotions(product, qty)
if not product.discount
""
elsif product.discount.kind_of? OneFreeDiscount
DiscountUtil.one_free_discount_to_s(product,qty)
elsif product.discount.kind_of? PackageDiscount
DiscountUtil.package_discount_to_s(product,qty)
elsif product.discount.kind_of? AmountDiscount
DiscountUtil.amount_discount_to_s(product,qty)
end
end
end
class Cart
INVOICE_SEPARATOR = "+------------------------------------------------+----------+\n"
INVOICE_HEADER_ROW = "| Name qty | price |\n"
attr_reader :items, :inventory
def initialize(inventory)
@inventory = inventory
@items = {}
@total = BigDecimal(0.to_s)
@coupons = {}
end
def add(product_name, quantity = 1)
validate_product(product_name)
validate_product_quantity(product_name, quantity)
if not @items[product_name]
@items[product_name] = quantity
else
@items[product_name] += quantity
end
end
def use(coupon_name)
if not @inventory.coupons[coupon_name]
raise "No such coupon"
else
@coupons[coupon_name] = 0
end
end
def total
@total = BigDecimal(0.to_s)
@items.each do |key, value|
@total += calculate_product_total(@inventory.products[key], value)
end
@coupons.each do |coupon, discount_amount|
old_total = @total
@total = @inventory.coupons[coupon].use(@total)
@coupons[coupon] = (@total < 0)? -old_total : (@total - old_total)
end
@total < 0 ? BigDecimal("0.00") : @total
end
def invoice
to_s
end
def to_s
@total = total
result = INVOICE_SEPARATOR + INVOICE_HEADER_ROW + INVOICE_SEPARATOR
@items.each do |key, value|
result += InvoiceUtil.product_to_s(@inventory.products[key], value)
end
@coupons.each do |coupon, discount_amount|
result += InvoiceUtil.coupon_to_s(@inventory.coupons[coupon], discount_amount)
end
result += INVOICE_SEPARATOR + InvoiceUtil.total_to_s(@total) + INVOICE_SEPARATOR
result
end
#private methods
private
def validate_product(product_name)
if not @inventory.products[product_name]
raise "No such product"
end
end
def validate_product_quantity(product_name, quantity)
if quantity < 0 or quantity > 99
raise "Bad product quantity"
end
if @items[product_name]
if @items[product_name] + quantity < 0
raise "Too few items of this kind in the cart"
elsif @items[product_name] + quantity > 99
raise "Too many items of this kind in the cart"
end
end
end
def calculate_product_total(product, quantity)
product.calculate_product_total(quantity)
end
end

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

...................

Finished in 0.57585 seconds
19 examples, 0 failures

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

Мартин обнови решението на 02.11.2011 23:50 (преди около 13 години)

+require 'bigdecimal'
+require 'bigdecimal/util'
+#TODO refactor/cleanup + use modules instead of utils if there's time
+class Product
+ attr_reader :name, :price, :discount
+ def initialize(name, price, discount)
+ validate_name(name)
+ validate_price(price)
+ @name = name
+ @price = price.to_d
+ @discount = DiscountUtil.get_discount_instance(discount)
+ end
+
+ #returns the total amount of money the user should pay for the items bought
+ def calculate_product_total(items_bought)
+ result = items_bought * @price
+ if @discount
+ result -= @discount.apply(self, items_bought)
+ end
+ result
+ end
+
+ private
+ def validate_name(product_name)
+ if product_name.length > 40
+ raise "Product name is too long"
+ end
+ end
+
+ def validate_price(product_price)
+ parsed_price = product_price.to_d
+ if parsed_price < 0.01 or parsed_price > 999.99
+ raise "Incorrect product price"
+ end
+ end
+end
+
+class DiscountUtil
+ def self.get_discount_instance(discount_hash)
+ if discount_hash[:get_one_free]
+ OneFreeDiscount.new(discount_hash[:get_one_free])
+ elsif discount_hash[:package]
+ PackageDiscount.new(discount_hash[:package])
+ elsif discount_hash[:threshold]
+ AmountDiscount.new(discount_hash[:threshold])
+ else
+ nil
+ end
+ end
+end
+
+class BaseDiscount
+ def apply(product, items_bought)
+ raise "This should be overriden"
+ end
+end
+
+class OneFreeDiscount < BaseDiscount
+ attr_reader :item_at, :free_items
+ def initialize(item_at_is_free)
+ @item_at = item_at_is_free
+ end
+
+ def apply(product, items_bought)
+ @free_items = items_bought / @item_at
+ res = @free_items * product.price
+ while @free_items / @item_at > 0
+ @free_items = @free_items / @item_at
+ end
+ res
+ end
+end
+
+class PackageDiscount < BaseDiscount
+ attr_reader :percent, :discount_for_every
+ def initialize(package_discount_hash)
+ package_discount_hash.each do |key, value|
+ @discount_for_every = key
+ @percent = value
+ end
+ end
+
+ #returns the amount of the discount
+ def apply(product, items_bought)
+ times_discount_applied = items_bought / @discount_for_every
+ percent = BigDecimal((@percent/100.to_f).to_s)
+ discounted_items = BigDecimal((times_discount_applied * @discount_for_every).to_s)
+ percent * discounted_items * product.price
+ end
+end
+
+class AmountDiscount < BaseDiscount
+ attr_reader :threshold, :percent
+ def initialize(amount_discount_hash)
+ amount_discount_hash.each do |key, value|
+ @threshold = key
+ @percent = value
+ end
+ end
+
+ #returns the amount of the discount
+ def apply(product, items_bought)
+ if items_bought < @threshold
+ 0
+ else
+ percent = BigDecimal((@percent/100.to_f).to_s)
+ discounted_items = BigDecimal((items_bought - @threshold).to_s)
+ percent * discounted_items * product.price
+ end
+ end
+end
+
+
+class CouponUtil
+ def self.get_coupon_instance(coupon_name, discount_hash)
+ if discount_hash[:percent]
+ PercentDiscountCoupon.new(coupon_name,discount_hash[:percent])
+ else
+ FixedAmountCoupon.new(coupon_name,discount_hash[:amount])
+ end
+ end
+end
+
+class BaseCoupon
+ def use(cart_items)
+ raise "this must be overriden"
+ end
+end
+
+class PercentDiscountCoupon < BaseCoupon
+ attr_reader :name, :percent
+ def initialize(coupon_name, percent)
+ @name = coupon_name
+ @percent = percent
+ end
+
+ def use(total)
+ percent = BigDecimal(((100 - @percent)/100.to_f).to_s)
+ percent * total
+ end
+end
+
+class FixedAmountCoupon < BaseCoupon
+ attr_reader :name, :amount
+ def initialize(coupon_name, discount_amount)
+ @name = coupon_name
+ @amount= discount_amount
+ end
+
+ def use(total)
+ total - @amount
+ end
+end
+
+class Inventory
+ attr_reader :products, :coupons
+ def initialize
+ @products = {}
+ @coupons = {}
+ end
+
+ def register(product_name, product_price, product_discount={})
+ if not @products[product_name]
+ @products[product_name] = Product.new(product_name, product_price, product_discount)
+ end
+ end
+
+ def register_coupon(coupon_name, discount_hash)
+ @coupons[coupon_name] = CouponUtil.get_coupon_instance(coupon_name, discount_hash)
+ end
+
+ def new_cart
+ Cart.new self
+ end
+end
+
+class FormatUtil
+ def self.format_number(number)
+ sprintf("%.2f", number)
+ end
+
+ def self.format_c(number)
+ if number == 1
+ suffix = "st"
+ elsif number == 2
+ suffix = "nd"
+ elsif number == 3
+ suffix = "rd"
+ else
+ suffix = "th"
+ end
+ number.to_s + suffix
+ end
+end
+
+class DiscountUtil
+ NAME_QTY_FIELD_LENGTH = 48
+ PRICE_FIELD_LENGTH = 10
+ def self.one_free_discount_to_s(product, items_bought)
+ items_to_buy = product.discount.item_at - 1
+ res = "| (buy #{items_to_buy}, get #{product.discount.free_items} free)"
+ res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
+ discount = FormatUtil.format_number(-product.discount.apply(product, items_bought))
+ res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount + " |\n"
+ res
+ end
+
+ def self.package_discount_to_s(product,qty)
+ percent = product.discount.percent
+ for_every = product.discount.discount_for_every
+ res = "| (get #{percent}% off for every #{for_every})"
+ res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
+ discount = FormatUtil.format_number(-product.discount.apply(product,qty))
+ res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount +" |\n"
+ res
+ end
+
+ def self.amount_discount_to_s(product,qty)
+ percent = product.discount.percent
+ threshold = product.discount.threshold
+ res = "| (#{percent}% off of every after the #{FormatUtil.format_c(threshold)})"
+ res += " " * (NAME_QTY_FIELD_LENGTH - res.length + 1) + "|"
+ discount = FormatUtil.format_number(-product.discount.apply(product,qty))
+ res += (" " * (PRICE_FIELD_LENGTH - 1 - discount.to_s.length)) + discount + " |\n"
+ res
+ end
+end
+
+class InvoiceUtil
+ NAME_QTY_FIELD_LENGTH = 48
+ PRICE_FIELD_LENGTH = 10
+ def self.product_to_s(product, qty)
+ res = "| " + product.name + (" " * get_name_blank_spaces(product.name, qty))
+ res += qty.to_s+" |"
+ res += " " * get_price_blank_spaces(product.price * qty)
+ res += FormatUtil.format_number((product.price * qty).to_f) + " |\n"
+ res += attach_product_promotions(product, qty)
+ res
+ end
+
+ def self.total_to_s(total)
+ res = "| TOTAL |"
+ formatted_total = FormatUtil.format_number(total.to_f)
+ res += " " * get_price_blank_spaces(total) + formatted_total + " |\n"
+ res
+ end
+
+ def self.coupon_to_s(coupon, discount)
+ res = "| Coupon " + coupon.name + " - "
+ if coupon.kind_of? FixedAmountCoupon
+ res += FormatUtil.format_number(coupon.amount.to_f) + " off"
+ else
+ res += coupon.percent.to_s + "% off"
+ end
+ blank_spaces = NAME_QTY_FIELD_LENGTH - res.length + 1
+ res += " " * blank_spaces + "|"
+ res += " " * get_price_blank_spaces(discount)
+ res += FormatUtil.format_number(discount.to_f) + " |\n"
+ res
+ end
+
+ private
+ def self.get_price_blank_spaces(price)
+ PRICE_FIELD_LENGTH - FormatUtil.format_number(price.to_f).length - 1
+ end
+
+ def self.get_name_blank_spaces(name, qty)
+ NAME_QTY_FIELD_LENGTH - name.length - qty.to_s.length - 2
+ end
+
+ def self.attach_product_promotions(product, qty)
+ if not product.discount
+ ""
+ elsif product.discount.kind_of? OneFreeDiscount
+ DiscountUtil.one_free_discount_to_s(product,qty)
+ elsif product.discount.kind_of? PackageDiscount
+ DiscountUtil.package_discount_to_s(product,qty)
+ elsif product.discount.kind_of? AmountDiscount
+ DiscountUtil.amount_discount_to_s(product,qty)
+ end
+ end
+end
+
+class Cart
+ INVOICE_SEPARATOR = "+------------------------------------------------+----------+\n"
+ INVOICE_HEADER_ROW = "| Name qty | price |\n"
+ attr_reader :items, :inventory
+ def initialize(inventory)
+ @inventory = inventory
+ @items = {}
+ @total = BigDecimal(0.to_s)
+ @coupons = {}
+ end
+
+ def add(product_name, quantity = 1)
+ validate_product(product_name)
+ validate_product_quantity(product_name, quantity)
+ if not @items[product_name]
+ @items[product_name] = quantity
+ else
+ @items[product_name] += quantity
+ end
+ end
+
+ def use(coupon_name)
+ if not @inventory.coupons[coupon_name]
+ raise "No such coupon"
+ else
+ @coupons[coupon_name] = 0
+ end
+ end
+
+ def total
+ @total = BigDecimal(0.to_s)
+ @items.each do |key, value|
+ @total += calculate_product_total(@inventory.products[key], value)
+ end
+ @coupons.each do |coupon, discount_amount|
+ old_total = @total
+ @total = @inventory.coupons[coupon].use(@total)
+ @coupons[coupon] = (@total < 0)? -old_total : (@total - old_total)
+ end
+ @total < 0 ? BigDecimal("0.00") : @total
+ end
+
+ def invoice
+ to_s
+ end
+
+ def to_s
+ @total = total
+ result = INVOICE_SEPARATOR + INVOICE_HEADER_ROW + INVOICE_SEPARATOR
+ @items.each do |key, value|
+ result += InvoiceUtil.product_to_s(@inventory.products[key], value)
+ end
+ @coupons.each do |coupon, discount_amount|
+ result += InvoiceUtil.coupon_to_s(@inventory.coupons[coupon], discount_amount)
+ end
+ result += INVOICE_SEPARATOR + InvoiceUtil.total_to_s(@total) + INVOICE_SEPARATOR
+ result
+ end
+
+ #private methods
+ private
+ def validate_product(product_name)
+ if not @inventory.products[product_name]
+ raise "No such product"
+ end
+ end
+
+ def validate_product_quantity(product_name, quantity)
+ if quantity < 0 or quantity > 99
+ raise "Bad product quantity"
+ end
+ if @items[product_name]
+ if @items[product_name] + quantity < 0
+ raise "Too few items of this kind in the cart"
+ elsif @items[product_name] + quantity > 99
+ raise "Too many items of this kind in the cart"
+ end
+ end
+ end
+
+ def calculate_product_total(product, quantity)
+ product.calculate_product_total(quantity)
+ end
+end