Решение на Трета задача от Георги Събев

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

Към профила на Георги Събев

Резултати

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

Код

require 'bigdecimal'
require 'bigdecimal/util'
class Inventory
def initialize
@articles, @coupons = {}, {}
is_new = ->(available) { ->(name) { !self.send available, name } }
@article_constraints = Constraints.new custom: is_new.(:available?)
@coupon_constraints = Constraints.new custom: is_new.(:available_coupon?)
@article_length_constraints = Constraints.new in: 0..40
@price_constraints = Constraints.new in: (0.01..999.99)
only_one_discount = ->(discount) { discount.keys.size <= 1 }
@discount_constraints = Constraints.new custom: only_one_discount
end
def register article_name, price = '0.00', discount = {}
enforce_constraints article_name, price, discount
@articles[article_name] = Article.new article_name, price.to_d, discount
end
def register_coupon coupon_name, parameter = {}
@coupon_constraints.enforce coupon_name
@coupons[coupon_name] = Coupon.new coupon_name, parameter
end
def article article_name
@articles[article_name]
end
def coupon coupon_name
@coupons[coupon_name]
end
def available? article_name
@articles.has_key? article_name
end
def available_coupon? coupon_name
@coupons.has_key? coupon_name
end
def new_cart
Cart.new self
end
private
def enforce_constraints article_name, price, discount
@article_constraints.enforce article_name
@article_length_constraints.enforce article_name.length
@price_constraints.enforce price.to_d
@discount_constraints.enforce discount
end
end
class Article
attr_reader :name, :price, :discount
def initialize name, price, discount = {}
@name, @price, @discount = name, price, discount
end
def has_discount? quantity
@discount.size > 0 && calculate_discount(quantity) > 0
end
def discount_type
@discount.keys.first
end
def discount_name
contract = @discount[discount_type]
case discount_type
when :get_one_free then "(buy #{contract - 1}, get 1 free)"
when :package then "(get #{percent}% off for every #{count})"
when :threshold then "(#{percent}% off of every after the #{ordinal count})"
end
end
def calculate_price quantity
price * quantity
end
def calculate_discount quantity
[case discount_type
when :get_one_free then (quantity / discount[:get_one_free]) * price
when :package then (quantity / count) * (percent / 100.0) * calculate_price(count)
when :threshold then (quantity - count) * (percent / 100.0) * price
else 0
end, 0].max
end
private
def count
@discount[discount_type].keys.first
end
def percent
@discount[discount_type].values.first
end
def ordinal num
case num
when 1 then "1st"
when 2 then "2nd"
when 3 then "3rd"
else "#{num}th"
end
end
end
class Coupon
attr_reader :name
def initialize name, parameter
@name, @parameter = name, parameter
end
def calculate_discount total
discount = [case coupon_type
when :amount then @parameter[:amount].to_d
when :percent then total * (@parameter[:percent] / 100.0)
else total
end, 0].max
[total, discount].min
end
def to_s
discount = case coupon_type
when :amount then @parameter[:amount]
when :percent then "#{@parameter[:percent]}%"
else 0
end
"Coupon #{name} - #{discount} off"
end
private
def coupon_type
@parameter.keys.first
end
end
class Cart
attr_reader :coupon
def initialize inventory
@inventory = inventory
@cart = Hash.new(0)
exists = ->(available) { ->(name) { @inventory.send available, name } }
@article_constraints = Constraints.new custom: exists.(:available?)
@coupon_constraints = Constraints.new custom: exists.(:available_coupon?)
@quantity_constraints = Constraints.new in: 0..99
end
def add article_name, quantity = 1
enforce_constraints article_name, quantity
@cart[article_name] += quantity
end
def use coupon_name
@coupon_constraints.enforce coupon_name
@coupon = @inventory.coupon coupon_name
end
def total_no_coupon
total = @cart.inject(0) do |result, (article_name, quantity)|
article = @inventory.article article_name
result + article.calculate_price(quantity) - article.calculate_discount(quantity)
end
end
def total
discount = (@coupon.calculate_discount(total_no_coupon) if @coupon) || 0
[total_no_coupon - discount, 0].max
end
def invoice
Invoice.new(self).to_s
end
def quantity article
@cart[article.name]
end
def articles
@cart.keys.map { |article_name| @inventory.article article_name }
end
private
def enforce_constraints article_name, quantity
@article_constraints.enforce article_name
@quantity_constraints.enforce quantity
article = @inventory.article article_name
@quantity_constraints.enforce quantity(article) + quantity
end
end
class Invoice
NameColumnSize = 48
PriceColumnSize = 10
def initialize cart
@cart = cart
end
def to_s
table = Array(header)
table += @cart.articles.map { |article| article_row article }
table += coupon_row if @cart.coupon
table += footer
table.join "\n"
end
private
def article_row article
quantity = @cart.quantity(article)
price = format_price article.calculate_price(quantity)
row = [table_row(article.name, quantity, price)]
discount_row = discount_row article, quantity if article.has_discount? quantity
row += Array(discount_row)
end
def discount_row article, quantity
discount = article.calculate_discount(quantity) * -1
discount_row = table_row article.discount_name, "", format_price(discount), 3
end
def coupon_row
discount = @cart.coupon.calculate_discount(@cart.total_no_coupon) * -1 || 0
coupon_row = table_row @cart.coupon.to_s, "", format_price(discount)
Array(coupon_row)
end
def table_row name, quantity, price, indent = 1
name_width = NameColumnSize - quantity.to_s.length - indent - 1
name_format = " " * indent + "%-#{name_width}s#{quantity} "
name_column = format name_format, name, quantity
price_format = "%#{PriceColumnSize - 1}s "
price_column = format price_format, price
["", name_column, price_column, ""].join "|"
end
def header
[separator, table_row("Name", "qty", "price"), separator]
end
def footer
[separator, table_row("TOTAL", "", format_price(@cart.total)), separator, ""]
end
def separator
["", '-' * NameColumnSize, '-' * PriceColumnSize, ""].join '+'
end
def format_price price
format "%.2f", price
end
end
class Constraints
def initialize constraints = {}
@constraints = constraints
end
def enforce arg
validity_vector = @constraints.keys.map { |type| satisfies? type, arg }
raise build_error_message(validity_vector, arg) unless validity_vector.all?
end
private
def satisfies? constraint_type, arg
limits = @constraints[constraint_type]
case constraint_type
when :in then limits.include? arg
when :<, :<=, :==, :>, :>= then arg.send constraint_type, limits
when :custom then limits.(arg)
else true
end
end
def build_error_message validity_vector, arg
zipped_results = validity_vector.zip @constraints.keys
failed = zipped_results.select { |passed, constraint| constraint unless passed }
failed = failed.map do |passed, constraint|
"#{arg} #{constraint} #{@constraints[constraint]}"
end
"The following constraints were not satisfied: #{failed.to_s}"
end
end

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

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

Failures:

  1) Inventory with an 'amount off' coupon shows the discount in the invoice
     Failure/Error: cart.invoice.should eq <<INVOICE
       
       expected: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                    5 |     5.00 |\n| Coupon TEA-TIME - 10.00 off                    |    -5.00 |\n+------------------------------------------------+----------+\n| TOTAL                                          |     0.00 |\n+------------------------------------------------+----------+\n"
            got: "+------------------------------------------------+----------+\n| Name                                       qty |    price |\n+------------------------------------------------+----------+\n| Green Tea                                    5 |     5.00 |\n| Coupon TEA-TIME - 0.1E2 off                    |    -5.00 |\n+------------------------------------------------+----------+\n| TOTAL                                          |     0.00 |\n+------------------------------------------------+----------+\n"
       
       (compared using ==)
       
       Diff:
       @@ -2,7 +2,7 @@
        | Name                                       qty |    price |
        +------------------------------------------------+----------+
        | Green Tea                                    5 |     5.00 |
       -| Coupon TEA-TIME - 10.00 off                    |    -5.00 |
       +| Coupon TEA-TIME - 0.1E2 off                    |    -5.00 |
        +------------------------------------------------+----------+
        | TOTAL                                          |     0.00 |
        +------------------------------------------------+----------+
     # /tmp/d20111115-5847-j2y1gr/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)>'

Finished in 0.56957 seconds
19 examples, 1 failure

Failed examples:

rspec /tmp/d20111115-5847-j2y1gr/spec.rb:274 # Inventory with an 'amount off' coupon shows the discount in the invoice

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

Георги обнови решението на 06.11.2011 16:20 (преди над 12 години)

+require 'bigdecimal'
+require 'bigdecimal/util'
+
+class Inventory
+ def initialize
+ @articles, @coupons = {}, {}
+ is_new = ->(available) { ->(name) { !self.send available, name } }
+ @article_constraints = Constraints.new custom: is_new.(:available?)
+ @coupon_constraints = Constraints.new custom: is_new.(:available_coupon?)
+ @article_length_constraints = Constraints.new in: 0..40
+ @price_constraints = Constraints.new in: (0.01..999.99)
+ only_one_discount = ->(discount) { discount.keys.size <= 1 }
+ @discount_constraints = Constraints.new custom: only_one_discount
+ end
+
+
+ def register article_name, price = '0.00', discount = {}
+ enforce_constraints article_name, price, discount
+ @articles[article_name] = Article.new article_name, price.to_d, discount
+ end
+
+ def register_coupon coupon_name, parameter = {}
+ @coupon_constraints.enforce coupon_name
+ @coupons[coupon_name] = Coupon.new coupon_name, parameter
+ end
+
+ def article article_name
+ @articles[article_name]
+ end
+
+ def coupon coupon_name
+ @coupons[coupon_name]
+ end
+
+ def available? article_name
+ @articles.has_key? article_name
+ end
+
+ def available_coupon? coupon_name
+ @coupons.has_key? coupon_name
+ end
+
+ def new_cart
+ Cart.new self
+ end
+
+ private
+
+ def enforce_constraints article_name, price, discount
+ @article_constraints.enforce article_name
+ @article_length_constraints.enforce article_name.length
+ @price_constraints.enforce price.to_d
+ @discount_constraints.enforce discount
+ end
+end
+
+class Article
+ attr_reader :name, :price, :discount
+
+ def initialize name, price, discount = {}
+ @name, @price, @discount = name, price, discount
+ end
+
+ def has_discount? quantity
+ @discount.size > 0 && calculate_discount(quantity) > 0
+ end
+
+ def discount_type
+ @discount.keys.first
+ end
+
+ def discount_name
+ contract = @discount[discount_type]
+ case discount_type
+ when :get_one_free then "(buy #{contract - 1}, get 1 free)"
+ when :package then "(get #{percent}% off for every #{count})"
+ when :threshold then "(#{percent}% off of every after the #{ordinal count})"
+ end
+ end
+
+ def calculate_price quantity
+ price * quantity
+ end
+
+ def calculate_discount quantity
+ [case discount_type
+ when :get_one_free then (quantity / discount[:get_one_free]) * price
+ when :package then (quantity / count) * (percent / 100.0) * calculate_price(count)
+ when :threshold then (quantity - count) * (percent / 100.0) * price
+ else 0
+ end, 0].max
+ end
+
+ private
+
+ def count
+ @discount[discount_type].keys.first
+ end
+
+ def percent
+ @discount[discount_type].values.first
+ end
+
+ def ordinal num
+ case num
+ when 1 then "1st"
+ when 2 then "2nd"
+ when 3 then "3rd"
+ else "#{num}th"
+ end
+ end
+
+end
+
+class Coupon
+ attr_reader :name
+
+ def initialize name, parameter
+ @name, @parameter = name, parameter
+ end
+
+ def calculate_discount total
+ discount = [case coupon_type
+ when :amount then @parameter[:amount].to_d
+ when :percent then total * (@parameter[:percent] / 100.0)
+ else total
+ end, 0].max
+ [total, discount].min
+ end
+
+ def to_s
+ discount = case coupon_type
+ when :amount then @parameter[:amount]
+ when :percent then "#{@parameter[:percent]}%"
+ else 0
+ end
+ "Coupon #{name} - #{discount} off"
+ end
+
+ private
+
+ def coupon_type
+ @parameter.keys.first
+ end
+end
+
+class Cart
+ attr_reader :coupon
+
+ def initialize inventory
+ @inventory = inventory
+ @cart = Hash.new(0)
+ exists = ->(available) { ->(name) { @inventory.send available, name } }
+ @article_constraints = Constraints.new custom: exists.(:available?)
+ @coupon_constraints = Constraints.new custom: exists.(:available_coupon?)
+ @quantity_constraints = Constraints.new in: 0..99
+ end
+
+ def add article_name, quantity = 1
+ enforce_constraints article_name, quantity
+ @cart[article_name] += quantity
+ end
+
+ def use coupon_name
+ @coupon_constraints.enforce coupon_name
+ @coupon = @inventory.coupon coupon_name
+ end
+
+ def total_no_coupon
+ total = @cart.inject(0) do |result, (article_name, quantity)|
+ article = @inventory.article article_name
+ result + article.calculate_price(quantity) - article.calculate_discount(quantity)
+ end
+ end
+
+ def total
+ discount = (@coupon.calculate_discount(total_no_coupon) if @coupon) || 0
+ [total_no_coupon - discount, 0].max
+ end
+
+ def invoice
+ Invoice.new(self).to_s
+ end
+
+ def quantity article
+ @cart[article.name]
+ end
+
+ def articles
+ @cart.keys.map { |article_name| @inventory.article article_name }
+ end
+
+ private
+
+ def enforce_constraints article_name, quantity
+ @article_constraints.enforce article_name
+ @quantity_constraints.enforce quantity
+ article = @inventory.article article_name
+ @quantity_constraints.enforce quantity(article) + quantity
+ end
+end
+
+class Invoice
+ NameColumnSize = 48
+ PriceColumnSize = 10
+
+ def initialize cart
+ @cart = cart
+ end
+
+ def to_s
+ table = Array(header)
+ table += @cart.articles.map { |article| article_row article }
+ table += coupon_row if @cart.coupon
+ table += footer
+ table.join "\n"
+ end
+
+ private
+
+ def article_row article
+ quantity = @cart.quantity(article)
+ price = format_price article.calculate_price(quantity)
+ row = [table_row(article.name, quantity, price)]
+ discount_row = discount_row article, quantity if article.has_discount? quantity
+ row += Array(discount_row)
+ end
+
+ def discount_row article, quantity
+ discount = article.calculate_discount(quantity) * -1
+ discount_row = table_row article.discount_name, "", format_price(discount), 3
+ end
+
+ def coupon_row
+ discount = @cart.coupon.calculate_discount(@cart.total_no_coupon) * -1 || 0
+ coupon_row = table_row @cart.coupon.to_s, "", format_price(discount)
+ Array(coupon_row)
+ end
+
+ def table_row name, quantity, price, indent = 1
+ name_width = NameColumnSize - quantity.to_s.length - indent - 1
+ name_format = " " * indent + "%-#{name_width}s#{quantity} "
+ name_column = format name_format, name, quantity
+ price_format = "%#{PriceColumnSize - 1}s "
+ price_column = format price_format, price
+ ["", name_column, price_column, ""].join "|"
+ end
+
+ def header
+ [separator, table_row("Name", "qty", "price"), separator]
+ end
+
+ def footer
+ [separator, table_row("TOTAL", "", format_price(@cart.total)), separator, ""]
+ end
+
+ def separator
+ ["", '-' * NameColumnSize, '-' * PriceColumnSize, ""].join '+'
+ end
+
+ def format_price price
+ format "%.2f", price
+ end
+end
+
+class Constraints
+ def initialize constraints = {}
+ @constraints = constraints
+ end
+
+ def enforce arg
+ validity_vector = @constraints.keys.map { |type| satisfies? type, arg }
+ raise build_error_message(validity_vector, arg) unless validity_vector.all?
+ end
+
+ private
+
+ def satisfies? constraint_type, arg
+ limits = @constraints[constraint_type]
+ case constraint_type
+ when :in then limits.include? arg
+ when :<, :<=, :==, :>, :>= then arg.send constraint_type, limits
+ when :custom then limits.(arg)
+ else true
+ end
+ end
+
+ def build_error_message validity_vector, arg
+ zipped_results = validity_vector.zip @constraints.keys
+ failed = zipped_results.select { |passed, constraint| constraint unless passed }
+ failed = failed.map do |passed, constraint|
+ "#{arg} #{constraint} #{@constraints[constraint]}"
+ end
+ "The following constraints were not satisfied: #{failed.to_s}"
+ end
+end