diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/c/src/supermarket.c b/c/src/supermarket.c index 414ce1d..12b432c 100644 --- a/c/src/supermarket.c +++ b/c/src/supermarket.c @@ -3,6 +3,19 @@ #include #include "supermarket.h" +// Constants for magic numbers +#define DEFAULT_ITEM_QUANTITY 1.0 +#define TWO_FOR_AMOUNT_QUANTITY 2 +#define THREE_FOR_TWO_QUANTITY 3 +#define FIVE_FOR_AMOUNT_QUANTITY 5 +#define PERCENTAGE_DIVISOR 100.0 + +// Constants for discount description strings +#define THREE_FOR_TWO_DESCRIPTION "3 for 2" +#define TWO_FOR_FORMAT "2 for %f" +#define FOR_FORMAT "%d for %f" +#define PERCENT_OFF_FORMAT "%.0f%% off" + struct product_t* product_create(char* name, enum unit unit) { struct product_t* product = malloc(sizeof(*product)); strncpy(product->name, name, sizeof(product->name) - 1); @@ -76,36 +89,36 @@ void handle_offers(struct cart_t* cart, struct receipt_t* receipt, struct specia int x = 1; if (offer->type == ThreeForTwo) { - x = 3; + x = THREE_FOR_TWO_QUANTITY; } else if (offer->type == TwoForAmount) { - x = 2; - if (quantityAsInt >= 2) { - double total = offer->argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + x = TWO_FOR_AMOUNT_QUANTITY; + if (quantityAsInt >= TWO_FOR_AMOUNT_QUANTITY) { + double total = offer->argument * (quantityAsInt / x) + quantityAsInt % TWO_FOR_AMOUNT_QUANTITY * unitPrice; double discountN = unitPrice * quantity - total; char description[MAX_NAME_LENGTH]; - sprintf(description, "2 for %f", offer->argument); + sprintf(description, TWO_FOR_FORMAT, offer->argument); discount = discount_create(description, -discountN, &product); } } if (offer->type == FiveForAmount) { - x = 5; + x = FIVE_FOR_AMOUNT_QUANTITY; } int numberOfXs = quantityAsInt / x; - if (offer->type == ThreeForTwo && quantityAsInt > 2) { - double discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); + if (offer->type == ThreeForTwo && quantityAsInt > TWO_FOR_AMOUNT_QUANTITY) { + double discountAmount = quantity * unitPrice - ((numberOfXs * TWO_FOR_AMOUNT_QUANTITY * unitPrice) + quantityAsInt % THREE_FOR_TWO_QUANTITY * unitPrice); char description[MAX_NAME_LENGTH]; - sprintf(description, "3 for 2"); + sprintf(description, THREE_FOR_TWO_DESCRIPTION); discount = discount_create(description, -discountAmount, &product); } if (offer->type == TenPercentDiscount) { char description[MAX_NAME_LENGTH]; - sprintf(description, "%.0f%% off", offer->argument); - discount = discount_create(description, -quantity * unitPrice * offer->argument / 100.0, &product); + sprintf(description, PERCENT_OFF_FORMAT, offer->argument); + discount = discount_create(description, -quantity * unitPrice * offer->argument / PERCENTAGE_DIVISOR, &product); } - if (offer->type == FiveForAmount && quantityAsInt >= 5) { - double discountTotal = unitPrice * quantity - (offer->argument * numberOfXs + quantityAsInt % 5 * unitPrice); + if (offer->type == FiveForAmount && quantityAsInt >= FIVE_FOR_AMOUNT_QUANTITY) { + double discountTotal = unitPrice * quantity - (offer->argument * numberOfXs + quantityAsInt % FIVE_FOR_AMOUNT_QUANTITY * unitPrice); char description[MAX_NAME_LENGTH]; - sprintf(description, "%d for %f", x, offer->argument); + sprintf(description, FOR_FORMAT, x, offer->argument); discount = discount_create(description, -discountTotal, &product); } if (discount != NULL) { diff --git a/common-lisp/source/shopping-cart.lisp b/common-lisp/source/shopping-cart.lisp index 51631a2..ba7d1c0 100644 --- a/common-lisp/source/shopping-cart.lisp +++ b/common-lisp/source/shopping-cart.lisp @@ -2,6 +2,16 @@ (in-package :supermarket-receipt) +;;; Constants for magic numbers +(defconstant +default-item-quantity+ 1.0) +(defconstant +two-for-amount-quantity+ 2) +(defconstant +three-for-two-quantity+ 3) +(defconstant +five-for-amount-quantity+ 5) +(defconstant +percentage-divisor+ 100.0) + +;;; Constants for discount description strings +(defconstant +three-for-two-description+ "3 for 2") + (defclass shopping-cart () ((items :initform nil :type list @@ -11,7 +21,7 @@ :accessor shopping-cart-product-quantities))) (defmethod add-item ((a-cart shopping-cart) (an-item product)) - (add-item-quantity a-cart an-item 1.0)) + (add-item-quantity a-cart an-item +default-item-quantity+)) (defmethod add-item-quantity ((a-cart shopping-cart) (an-item product) (a-quantity single-float)) (push (make-instance 'product-quantity @@ -35,39 +45,39 @@ (x 1) (the-offer-type (offer-type offer-for-product))) (if (eq the-offer-type 'three-for-two) - (setf x 3) + (setf x +three-for-two-quantity+) (when (eq the-offer-type 'two-for-amount) - (setf x 2) - (when (>= floored-quantity 2) + (setf x +two-for-amount-quantity+) + (when (>= floored-quantity +two-for-amount-quantity+) (let* ((total (+ (* (offer-argument offer-for-product) (floor (/ floored-quantity x))) - (* (mod floored-quantity 2) a-unit-price))) + (* (mod floored-quantity +two-for-amount-quantity+) a-unit-price))) (discount-n (- (* a-unit-price a-quantity) total))) (setf a-discount (make-instance 'discount :product a-product :description (format nil "2 for ~S" (offer-argument offer-for-product)) :amount (- discount-n))))))) (when (eq the-offer-type 'five-for-amount) - (setf x 5)) + (setf x +five-for-amount-quantity+)) (let ((number-of-x (floor (/ floored-quantity x)))) (when (and (eq the-offer-type 'three-for-two) - (> floored-quantity 2)) + (> floored-quantity +two-for-amount-quantity+)) (let ((discount-amount (- (* a-quantity a-unit-price) - (+ (* number-of-x 2 a-unit-price) - (* (mod floored-quantity 3) a-unit-price))))) + (+ (* number-of-x +two-for-amount-quantity+ a-unit-price) + (* (mod floored-quantity +three-for-two-quantity+) a-unit-price))))) (setf a-discount (make-instance 'discount :product a-product - :description "3 for 2" + :description +three-for-two-description+ :amount (- discount-amount))))) (when (eq the-offer-type 'ten-percent-discount) (setf a-discount (make-instance 'discount :product a-product :description (format nil "~S % off" (offer-argument offer-for-product)) - :amount (/ (* (- a-quantity) a-unit-price (offer-argument offer-for-product)) 100.0)))) + :amount (/ (* (- a-quantity) a-unit-price (offer-argument offer-for-product)) +percentage-divisor+)))) (when (and (eq the-offer-type 'five-for-amount) - (>= floored-quantity 5)) + (>= floored-quantity +five-for-amount-quantity+)) (let ((discount-total (- (* a-quantity a-unit-price) (+ (* (offer-argument offer-for-product) number-of-x) - (* (mod floored-quantity 5) a-unit-price))))) + (* (mod floored-quantity +five-for-amount-quantity+) a-unit-price))))) (setf a-discount (make-instance 'discount :product a-product :description (format nil "~S for ~S" x (offer-argument offer-for-product)) diff --git a/cpp/src/ShoppingCart.cpp b/cpp/src/ShoppingCart.cpp index 3c3a523..faf4540 100644 --- a/cpp/src/ShoppingCart.cpp +++ b/cpp/src/ShoppingCart.cpp @@ -1,5 +1,20 @@ #include "ShoppingCart.h" +// Constants for magic numbers +namespace { + constexpr double DEFAULT_ITEM_QUANTITY = 1.0; + constexpr int TWO_FOR_AMOUNT_QUANTITY = 2; + constexpr int THREE_FOR_TWO_QUANTITY = 3; + constexpr int FIVE_FOR_AMOUNT_QUANTITY = 5; + constexpr double PERCENTAGE_DIVISOR = 100.0; + + // Constants for discount description strings + const std::string THREE_FOR_TWO_DESCRIPTION = "3 for 2"; + const std::string TWO_FOR_PREFIX = "2 for "; + const std::string FOR_SEPARATOR = " for "; + const std::string PERCENT_OFF_SUFFIX = "% off"; +} + void addItemQuantity(const Product& product, double quantity); std::vector ShoppingCart::getItems() const { @@ -11,7 +26,7 @@ std::map ShoppingCart::getProductQuantities() const { } void ShoppingCart::addItem(const Product& product) { - addItemQuantity(product, 1.0); + addItemQuantity(product, DEFAULT_ITEM_QUANTITY); } void ShoppingCart::addItemQuantity(const Product& product, double quantity) { @@ -35,28 +50,28 @@ void ShoppingCart::handleOffers(Receipt& receipt, std::map offer int x = 1; if (offer.getOfferType() == SpecialOfferType::ThreeForTwo) { - x = 3; + x = THREE_FOR_TWO_QUANTITY; } else if (offer.getOfferType() == SpecialOfferType::TwoForAmount) { - x = 2; - if (quantityAsInt >= 2) { - double total = offer.getArgument() * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + x = TWO_FOR_AMOUNT_QUANTITY; + if (quantityAsInt >= TWO_FOR_AMOUNT_QUANTITY) { + double total = offer.getArgument() * (quantityAsInt / x) + quantityAsInt % TWO_FOR_AMOUNT_QUANTITY * unitPrice; double discountN = unitPrice * quantity - total; - discount = new Discount("2 for " + std::to_string(offer.getArgument()), -discountN, product); + discount = new Discount(TWO_FOR_PREFIX + std::to_string(offer.getArgument()), -discountN, product); } } if (offer.getOfferType() == SpecialOfferType::FiveForAmount) { - x = 5; + x = FIVE_FOR_AMOUNT_QUANTITY; } int numberOfXs = quantityAsInt / x; - if (offer.getOfferType() == SpecialOfferType::ThreeForTwo && quantityAsInt > 2) { - double discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); - discount = new Discount("3 for 2", -discountAmount, product); + if (offer.getOfferType() == SpecialOfferType::ThreeForTwo && quantityAsInt > TWO_FOR_AMOUNT_QUANTITY) { + double discountAmount = quantity * unitPrice - ((numberOfXs * TWO_FOR_AMOUNT_QUANTITY * unitPrice) + quantityAsInt % THREE_FOR_TWO_QUANTITY * unitPrice); + discount = new Discount(THREE_FOR_TWO_DESCRIPTION, -discountAmount, product); } if (offer.getOfferType() == SpecialOfferType::TenPercentDiscount) { - discount = new Discount(std::to_string(offer.getArgument()) + "% off", -quantity * unitPrice * offer.getArgument() / 100.0, product); + discount = new Discount(std::to_string(offer.getArgument()) + PERCENT_OFF_SUFFIX, -quantity * unitPrice * offer.getArgument() / PERCENTAGE_DIVISOR, product); } - if (offer.getOfferType() == SpecialOfferType::FiveForAmount && quantityAsInt >= 5) { - double discountTotal = unitPrice * quantity - (offer.getArgument() * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(std::to_string(x) + " for " + std::to_string(offer.getArgument()), -discountTotal, product); + if (offer.getOfferType() == SpecialOfferType::FiveForAmount && quantityAsInt >= FIVE_FOR_AMOUNT_QUANTITY) { + double discountTotal = unitPrice * quantity - (offer.getArgument() * numberOfXs + quantityAsInt % FIVE_FOR_AMOUNT_QUANTITY * unitPrice); + discount = new Discount(std::to_string(x) + FOR_SEPARATOR + std::to_string(offer.getArgument()), -discountTotal, product); } if (discount != nullptr) receipt.addDiscount(*discount); diff --git a/csharp-ms/SupermarketReceipt/ShoppingCart.cs b/csharp-ms/SupermarketReceipt/ShoppingCart.cs index 28abbe7..4965286 100644 --- a/csharp-ms/SupermarketReceipt/ShoppingCart.cs +++ b/csharp-ms/SupermarketReceipt/ShoppingCart.cs @@ -4,6 +4,19 @@ namespace SupermarketReceipt { public class ShoppingCart { + // Constants for magic numbers + private const double DefaultItemQuantity = 1.0; + private const int TwoForAmountQuantity = 2; + private const int ThreeForTwoQuantity = 3; + private const int FiveForAmountQuantity = 5; + private const double PercentageDivisor = 100.0; + + // Constants for discount description strings + private const string ThreeForTwoDescription = "3 for 2"; + private const string TwoForPrefix = "2 for "; + private const string ForSeparator = " for "; + private const string PercentOffSuffix = "% off"; + private readonly List _items = new List(); private readonly Dictionary _productQuantities = new Dictionary(); @@ -15,7 +28,7 @@ public List GetItems() public void AddItem(Product product) { - AddItemQuantity(product, 1.0); + AddItemQuantity(product, DefaultItemQuantity); } @@ -47,32 +60,32 @@ public void HandleOffers(Receipt receipt, Dictionary offers, Sup var x = 1; if (offer.OfferType == SpecialOfferType.ThreeForTwo) { - x = 3; + x = ThreeForTwoQuantity; } else if (offer.OfferType == SpecialOfferType.TwoForAmount) { - x = 2; - if (quantityAsInt >= 2) + x = TwoForAmountQuantity; + if (quantityAsInt >= TwoForAmountQuantity) { - var total = offer.Argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + var total = offer.Argument * (quantityAsInt / x) + quantityAsInt % TwoForAmountQuantity * unitPrice; var discountN = unitPrice * quantity - total; - discount = new Discount(p, "2 for " + offer.Argument, -discountN); + discount = new Discount(p, TwoForPrefix + offer.Argument, -discountN); } } - if (offer.OfferType == SpecialOfferType.FiveForAmount) x = 5; + if (offer.OfferType == SpecialOfferType.FiveForAmount) x = FiveForAmountQuantity; var numberOfXs = quantityAsInt / x; - if (offer.OfferType == SpecialOfferType.ThreeForTwo && quantityAsInt > 2) + if (offer.OfferType == SpecialOfferType.ThreeForTwo && quantityAsInt > TwoForAmountQuantity) { - var discountAmount = quantity * unitPrice - (numberOfXs * 2 * unitPrice + quantityAsInt % 3 * unitPrice); - discount = new Discount(p, "3 for 2", -discountAmount); + var discountAmount = quantity * unitPrice - (numberOfXs * TwoForAmountQuantity * unitPrice + quantityAsInt % ThreeForTwoQuantity * unitPrice); + discount = new Discount(p, ThreeForTwoDescription, -discountAmount); } - if (offer.OfferType == SpecialOfferType.TenPercentDiscount) discount = new Discount(p, offer.Argument + "% off", -quantity * unitPrice * offer.Argument / 100.0); - if (offer.OfferType == SpecialOfferType.FiveForAmount && quantityAsInt >= 5) + if (offer.OfferType == SpecialOfferType.TenPercentDiscount) discount = new Discount(p, offer.Argument + PercentOffSuffix, -quantity * unitPrice * offer.Argument / PercentageDivisor); + if (offer.OfferType == SpecialOfferType.FiveForAmount && quantityAsInt >= FiveForAmountQuantity) { - var discountTotal = unitPrice * quantity - (offer.Argument * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(p, x + " for " + offer.Argument, -discountTotal); + var discountTotal = unitPrice * quantity - (offer.Argument * numberOfXs + quantityAsInt % FiveForAmountQuantity * unitPrice); + discount = new Discount(p, x + ForSeparator + offer.Argument, -discountTotal); } if (discount != null) diff --git a/csharp/SupermarketReceipt/ShoppingCart.cs b/csharp/SupermarketReceipt/ShoppingCart.cs index 72b6b4a..f2069e2 100644 --- a/csharp/SupermarketReceipt/ShoppingCart.cs +++ b/csharp/SupermarketReceipt/ShoppingCart.cs @@ -5,6 +5,19 @@ namespace SupermarketReceipt { public class ShoppingCart { + // Constants for magic numbers + private const double DefaultItemQuantity = 1.0; + private const int TwoForAmountQuantity = 2; + private const int ThreeForTwoQuantity = 3; + private const int FiveForAmountQuantity = 5; + private const double PercentageDivisor = 100.0; + + // Constants for discount description strings + private const string ThreeForTwoDescription = "3 for 2"; + private const string TwoForPrefix = "2 for "; + private const string ForSeparator = " for "; + private const string PercentOffSuffix = "% off"; + private readonly List _items = new List(); private readonly Dictionary _productQuantities = new Dictionary(); private static readonly CultureInfo Culture = CultureInfo.CreateSpecificCulture("en-GB"); @@ -17,7 +30,7 @@ public List GetItems() public void AddItem(Product product) { - AddItemQuantity(product, 1.0); + AddItemQuantity(product, DefaultItemQuantity); } @@ -49,32 +62,32 @@ public void HandleOffers(Receipt receipt, Dictionary offers, Sup var x = 1; if (offer.OfferType == SpecialOfferType.ThreeForTwo) { - x = 3; + x = ThreeForTwoQuantity; } else if (offer.OfferType == SpecialOfferType.TwoForAmount) { - x = 2; - if (quantityAsInt >= 2) + x = TwoForAmountQuantity; + if (quantityAsInt >= TwoForAmountQuantity) { - var total = offer.Argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + var total = offer.Argument * (quantityAsInt / x) + quantityAsInt % TwoForAmountQuantity * unitPrice; var discountN = unitPrice * quantity - total; - discount = new Discount(p, "2 for " + PrintPrice(offer.Argument), -discountN); + discount = new Discount(p, TwoForPrefix + PrintPrice(offer.Argument), -discountN); } } - if (offer.OfferType == SpecialOfferType.FiveForAmount) x = 5; + if (offer.OfferType == SpecialOfferType.FiveForAmount) x = FiveForAmountQuantity; var numberOfXs = quantityAsInt / x; - if (offer.OfferType == SpecialOfferType.ThreeForTwo && quantityAsInt > 2) + if (offer.OfferType == SpecialOfferType.ThreeForTwo && quantityAsInt > TwoForAmountQuantity) { - var discountAmount = quantity * unitPrice - (numberOfXs * 2 * unitPrice + quantityAsInt % 3 * unitPrice); - discount = new Discount(p, "3 for 2", -discountAmount); + var discountAmount = quantity * unitPrice - (numberOfXs * TwoForAmountQuantity * unitPrice + quantityAsInt % ThreeForTwoQuantity * unitPrice); + discount = new Discount(p, ThreeForTwoDescription, -discountAmount); } - if (offer.OfferType == SpecialOfferType.TenPercentDiscount) discount = new Discount(p, offer.Argument + "% off", -quantity * unitPrice * offer.Argument / 100.0); - if (offer.OfferType == SpecialOfferType.FiveForAmount && quantityAsInt >= 5) + if (offer.OfferType == SpecialOfferType.TenPercentDiscount) discount = new Discount(p, offer.Argument + PercentOffSuffix, -quantity * unitPrice * offer.Argument / PercentageDivisor); + if (offer.OfferType == SpecialOfferType.FiveForAmount && quantityAsInt >= FiveForAmountQuantity) { - var discountTotal = unitPrice * quantity - (offer.Argument * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(p, x + " for " + PrintPrice(offer.Argument), -discountTotal); + var discountTotal = unitPrice * quantity - (offer.Argument * numberOfXs + quantityAsInt % FiveForAmountQuantity * unitPrice); + discount = new Discount(p, x + ForSeparator + PrintPrice(offer.Argument), -discountTotal); } if (discount != null) diff --git a/elixir/lib/supermarket/model/shopping_cart.ex b/elixir/lib/supermarket/model/shopping_cart.ex index b0fc089..463b784 100644 --- a/elixir/lib/supermarket/model/shopping_cart.ex +++ b/elixir/lib/supermarket/model/shopping_cart.ex @@ -5,12 +5,22 @@ defmodule Supermarket.Model.ShoppingCart do alias Supermarket.Model.Receipt alias Supermarket.Model.SupermarketCatalog + # Constants for magic numbers + @default_item_quantity 1.0 + @two_for_amount_quantity 2 + @three_for_two_quantity 3 + @five_for_amount_quantity 5 + @percentage_divisor 100.0 + + # Constants for discount description strings + @three_for_two_description "3 for 2" + defstruct [:items, :product_quantities] def new, do: %__MODULE__{items: [], product_quantities: %{}} def add_item(cart, product) do - add_item_quantity(cart, product, 1.0) + add_item_quantity(cart, product, @default_item_quantity) end def add_item_quantity(cart, product, quantity) do @@ -41,48 +51,48 @@ defmodule Supermarket.Model.ShoppingCart do {discount, x} = cond do offer.offer_type == :three_for_two -> - {discount, 3} + {discount, @three_for_two_quantity} offer.offer_type == :two_for_amount -> - if quantity_as_int >= 2 do - x = 2 + if quantity_as_int >= @two_for_amount_quantity do + x = @two_for_amount_quantity int_division = div(quantity_as_int, x) price_per_unit = offer.argument * int_division - the_total = Integer.mod(quantity_as_int, 2) * unit_price + the_total = Integer.mod(quantity_as_int, @two_for_amount_quantity) * unit_price total = price_per_unit + the_total discount_n = unit_price * quantity - total - {Discount.new(p, "2 for #{offer.argument}", -discount_n), 2} + {Discount.new(p, "2 for #{offer.argument}", -discount_n), @two_for_amount_quantity} else {discount, x} end true -> - {discount, 2} + {discount, @two_for_amount_quantity} end - x = if offer.offer_type == :five_for_amount, do: 5, else: x + x = if offer.offer_type == :five_for_amount, do: @five_for_amount_quantity, else: x number_of_xs = div(quantity_as_int, x) discount = cond do - offer.offer_type == :three_for_two and quantity_as_int > 2 -> + offer.offer_type == :three_for_two and quantity_as_int > @two_for_amount_quantity -> discount_amount = quantity * unit_price - - (number_of_xs * 2 * unit_price + Integer.mod(quantity_as_int, 3) * unit_price) + (number_of_xs * @two_for_amount_quantity * unit_price + Integer.mod(quantity_as_int, @three_for_two_quantity) * unit_price) - Discount.new(p, "3 for 2", -discount_amount) + Discount.new(p, @three_for_two_description, -discount_amount) offer.offer_type == :ten_percent_discount -> Discount.new( p, "#{offer.argument}% off", - -quantity * unit_price * offer.argument / 100.0 + -quantity * unit_price * offer.argument / @percentage_divisor ) - offer.offer_type == :five_for_amount and quantity_as_int >= 5 -> + offer.offer_type == :five_for_amount and quantity_as_int >= @five_for_amount_quantity -> discount_total = unit_price * quantity - - (offer.argument * number_of_xs + Integer.mod(quantity_as_int, 5) * unit_price) + (offer.argument * number_of_xs + Integer.mod(quantity_as_int, @five_for_amount_quantity) * unit_price) Discount.new(p, "#{x} for #{offer.argument}", -discount_total) diff --git a/go/supermarket/shopping_cart.go b/go/supermarket/shopping_cart.go index 3079f92..9736ac2 100644 --- a/go/supermarket/shopping_cart.go +++ b/go/supermarket/shopping_cart.go @@ -5,6 +5,23 @@ import ( "math" ) +// Constants for magic numbers +const ( + defaultItemQuantity = 1.0 + twoForAmountQuantity = 2 + threeForTwoQuantity = 3 + fiveForAmountQuantity = 5 + percentageDivisor = 100.0 +) + +// Constants for discount description strings +const ( + threeForTwoDescription = "3 for 2" + twoForPrefix = "2 for %.2f" + forSeparatorFormat = "%d for %.2f" + percentOffSuffixFormat = "%.0f %% off" +) + type ProductQuantity struct { product Product quantity float64 @@ -23,7 +40,7 @@ func NewShoppingCart() *ShoppingCart { } func (c *ShoppingCart) addItem(product Product) { - c.addItemQuantity(product, 1) + c.addItemQuantity(product, defaultItemQuantity) } func (c *ShoppingCart) addItemQuantity(product Product, amount float64) { @@ -45,31 +62,31 @@ func (c *ShoppingCart) handleOffers(receipt *Receipt, offers map[Product]Special var discount *Discount = nil var x = 1 if offer.offerType == ThreeForTwo { - x = 3 + x = threeForTwoQuantity } else if offer.offerType == TwoForAmount { - x = 2 - if quantityAsInt >= 2 { - var total = offer.argument * float64(quantityAsInt / x) + float64(quantityAsInt % 2) * unitPrice + x = twoForAmountQuantity + if quantityAsInt >= twoForAmountQuantity { + var total = offer.argument * float64(quantityAsInt / x) + float64(quantityAsInt % twoForAmountQuantity) * unitPrice var discountN = unitPrice * quantity - total; - discount = &Discount{product: p, description: fmt.Sprintf("2 for %.2f", offer.argument), discountAmount: -discountN} + discount = &Discount{product: p, description: fmt.Sprintf(twoForPrefix, offer.argument), discountAmount: -discountN} } } if offer.offerType == FiveForAmount { - x = 5 + x = fiveForAmountQuantity } var numberOfXs int = quantityAsInt / x; - if offer.offerType == ThreeForTwo && quantityAsInt > 2 { - var discountAmount = quantity * unitPrice - (float64(numberOfXs * 2) * unitPrice + float64(quantityAsInt % 3) * unitPrice) - discount = &Discount{product: p, description: "3 for 2", discountAmount: -discountAmount} + if offer.offerType == ThreeForTwo && quantityAsInt > twoForAmountQuantity { + var discountAmount = quantity * unitPrice - (float64(numberOfXs * twoForAmountQuantity) * unitPrice + float64(quantityAsInt % threeForTwoQuantity) * unitPrice) + discount = &Discount{product: p, description: threeForTwoDescription, discountAmount: -discountAmount} } if offer.offerType == TenPercentDiscount { - discount = &Discount{product: p, description: fmt.Sprintf("%.0f %% off", offer.argument), discountAmount: -quantity * unitPrice * offer.argument / 100.0} + discount = &Discount{product: p, description: fmt.Sprintf(percentOffSuffixFormat, offer.argument), discountAmount: -quantity * unitPrice * offer.argument / percentageDivisor} } - if offer.offerType == FiveForAmount && quantityAsInt >= 5 { - var discountTotal = unitPrice * quantity - (offer.argument * float64(numberOfXs) + float64(quantityAsInt % 5) * unitPrice) - discount = &Discount{product: p, description: fmt.Sprintf("%d for %.2f", x, offer.argument), discountAmount: -discountTotal} + if offer.offerType == FiveForAmount && quantityAsInt >= fiveForAmountQuantity { + var discountTotal = unitPrice * quantity - (offer.argument * float64(numberOfXs) + float64(quantityAsInt % fiveForAmountQuantity) * unitPrice) + discount = &Discount{product: p, description: fmt.Sprintf(forSeparatorFormat, x, offer.argument), discountAmount: -discountTotal} } if discount != nil { receipt.addDiscount(*discount) diff --git a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java index 9293086..844e725 100644 --- a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java +++ b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java @@ -8,6 +8,19 @@ public class ShoppingCart { + // Constants for magic numbers + private static final double DEFAULT_ITEM_QUANTITY = 1.0; + private static final int TWO_FOR_AMOUNT_QUANTITY = 2; + private static final int THREE_FOR_TWO_QUANTITY = 3; + private static final int FIVE_FOR_AMOUNT_QUANTITY = 5; + private static final double PERCENTAGE_DIVISOR = 100.0; + + // Constants for discount description strings + private static final String THREE_FOR_TWO_DESCRIPTION = "3 for 2"; + private static final String TWO_FOR_PREFIX = "2 for "; + private static final String FOR_SEPARATOR = " for "; + private static final String PERCENT_OFF_SUFFIX = "% off"; + private final List items = new ArrayList<>(); private final Map productQuantities = new HashMap<>(); @@ -16,7 +29,7 @@ List getItems() { } void addItem(Product product) { - addItemQuantity(product, 1.0); + addItemQuantity(product, DEFAULT_ITEM_QUANTITY); } Map productQuantities() { @@ -42,30 +55,30 @@ void handleOffers(Receipt receipt, Map offers, SupermarketCatalo Discount discount = null; int x = 1; if (offer.offerType == SpecialOfferType.THREE_FOR_TWO) { - x = 3; + x = THREE_FOR_TWO_QUANTITY; } else if (offer.offerType == SpecialOfferType.TWO_FOR_AMOUNT) { - x = 2; - if (quantityAsInt >= 2) { - double total = offer.argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + x = TWO_FOR_AMOUNT_QUANTITY; + if (quantityAsInt >= TWO_FOR_AMOUNT_QUANTITY) { + double total = offer.argument * (quantityAsInt / x) + quantityAsInt % TWO_FOR_AMOUNT_QUANTITY * unitPrice; double discountN = unitPrice * quantity - total; - discount = new Discount(p, "2 for " + offer.argument, -discountN); + discount = new Discount(p, TWO_FOR_PREFIX + offer.argument, -discountN); } } if (offer.offerType == SpecialOfferType.FIVE_FOR_AMOUNT) { - x = 5; + x = FIVE_FOR_AMOUNT_QUANTITY; } int numberOfXs = quantityAsInt / x; - if (offer.offerType == SpecialOfferType.THREE_FOR_TWO && quantityAsInt > 2) { - double discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); - discount = new Discount(p, "3 for 2", -discountAmount); + if (offer.offerType == SpecialOfferType.THREE_FOR_TWO && quantityAsInt > TWO_FOR_AMOUNT_QUANTITY) { + double discountAmount = quantity * unitPrice - ((numberOfXs * TWO_FOR_AMOUNT_QUANTITY * unitPrice) + quantityAsInt % THREE_FOR_TWO_QUANTITY * unitPrice); + discount = new Discount(p, THREE_FOR_TWO_DESCRIPTION, -discountAmount); } if (offer.offerType == SpecialOfferType.TEN_PERCENT_DISCOUNT) { - discount = new Discount(p, offer.argument + "% off", -quantity * unitPrice * offer.argument / 100.0); + discount = new Discount(p, offer.argument + PERCENT_OFF_SUFFIX, -quantity * unitPrice * offer.argument / PERCENTAGE_DIVISOR); } - if (offer.offerType == SpecialOfferType.FIVE_FOR_AMOUNT && quantityAsInt >= 5) { - double discountTotal = unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(p, x + " for " + offer.argument, -discountTotal); + if (offer.offerType == SpecialOfferType.FIVE_FOR_AMOUNT && quantityAsInt >= FIVE_FOR_AMOUNT_QUANTITY) { + double discountTotal = unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % FIVE_FOR_AMOUNT_QUANTITY * unitPrice); + discount = new Discount(p, x + FOR_SEPARATOR + offer.argument, -discountTotal); } if (discount != null) receipt.addDiscount(discount); diff --git a/kotlin/src/main/kotlin/supermarket/model/ShoppingCart.kt b/kotlin/src/main/kotlin/supermarket/model/ShoppingCart.kt index 83e7f30..2b43f4f 100644 --- a/kotlin/src/main/kotlin/supermarket/model/ShoppingCart.kt +++ b/kotlin/src/main/kotlin/supermarket/model/ShoppingCart.kt @@ -3,6 +3,19 @@ package supermarket.model import java.util.ArrayList import java.util.HashMap +// Constants for magic numbers +private const val DEFAULT_ITEM_QUANTITY = 1.0 +private const val TWO_FOR_AMOUNT_QUANTITY = 2 +private const val THREE_FOR_TWO_QUANTITY = 3 +private const val FIVE_FOR_AMOUNT_QUANTITY = 5 +private const val PERCENTAGE_DIVISOR = 100.0 + +// Constants for discount description strings +private const val THREE_FOR_TWO_DESCRIPTION = "3 for 2" +private const val TWO_FOR_PREFIX = "2 for " +private const val FOR_SEPARATOR = " for " +private const val PERCENT_OFF_SUFFIX = "% off" + class ShoppingCart { private val items = ArrayList() @@ -14,7 +27,7 @@ class ShoppingCart { } internal fun addItem(product: Product) { - this.addItemQuantity(product, 1.0) + this.addItemQuantity(product, DEFAULT_ITEM_QUANTITY) } internal fun productQuantities(): Map { @@ -41,34 +54,34 @@ class ShoppingCart { var discount: Discount? = null var x = 1 if (offer.offerType === SpecialOfferType.ThreeForTwo) { - x = 3 + x = THREE_FOR_TWO_QUANTITY } else if (offer.offerType === SpecialOfferType.TwoForAmount) { - x = 2 - if (quantityAsInt >= 2) { - val total = offer.argument * (quantityAsInt / x) + quantityAsInt % 2 * unitPrice + x = TWO_FOR_AMOUNT_QUANTITY + if (quantityAsInt >= TWO_FOR_AMOUNT_QUANTITY) { + val total = offer.argument * (quantityAsInt / x) + quantityAsInt % TWO_FOR_AMOUNT_QUANTITY * unitPrice val discountN = unitPrice * quantity - total - discount = Discount(p, "2 for " + offer.argument, discountN) + discount = Discount(p, TWO_FOR_PREFIX + offer.argument, discountN) } } if (offer.offerType === SpecialOfferType.FiveForAmount) { - x = 5 + x = FIVE_FOR_AMOUNT_QUANTITY } val numberOfXs = quantityAsInt / x - if (offer.offerType === SpecialOfferType.ThreeForTwo && quantityAsInt > 2) { + if (offer.offerType === SpecialOfferType.ThreeForTwo && quantityAsInt > TWO_FOR_AMOUNT_QUANTITY) { val discountAmount = - quantity * unitPrice - (numberOfXs.toDouble() * 2.0 * unitPrice + quantityAsInt % 3 * unitPrice) - discount = Discount(p, "3 for 2", discountAmount) + quantity * unitPrice - (numberOfXs.toDouble() * TWO_FOR_AMOUNT_QUANTITY.toDouble() * unitPrice + quantityAsInt % THREE_FOR_TWO_QUANTITY * unitPrice) + discount = Discount(p, THREE_FOR_TWO_DESCRIPTION, discountAmount) } if (offer.offerType === SpecialOfferType.TenPercentDiscount) { discount = - Discount(p, offer.argument.toString() + "% off", quantity * unitPrice * offer.argument / 100.0) + Discount(p, offer.argument.toString() + PERCENT_OFF_SUFFIX, quantity * unitPrice * offer.argument / PERCENTAGE_DIVISOR) } - if (offer.offerType === SpecialOfferType.FiveForAmount && quantityAsInt >= 5) { + if (offer.offerType === SpecialOfferType.FiveForAmount && quantityAsInt >= FIVE_FOR_AMOUNT_QUANTITY) { val discountTotal = - unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % 5 * unitPrice) - discount = Discount(p, x.toString() + " for " + offer.argument, discountTotal) + unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % FIVE_FOR_AMOUNT_QUANTITY * unitPrice) + discount = Discount(p, x.toString() + FOR_SEPARATOR + offer.argument, discountTotal) } if (discount != null) receipt.addDiscount(discount) diff --git a/php/src/Model/ShoppingCart.php b/php/src/Model/ShoppingCart.php index d2f6c78..38159b2 100644 --- a/php/src/Model/ShoppingCart.php +++ b/php/src/Model/ShoppingCart.php @@ -8,6 +8,19 @@ class ShoppingCart { + // Constants for magic numbers + private const DEFAULT_ITEM_QUANTITY = 1.0; + private const TWO_FOR_AMOUNT_QUANTITY = 2; + private const THREE_FOR_TWO_QUANTITY = 3; + private const FIVE_FOR_AMOUNT_QUANTITY = 5; + private const PERCENTAGE_DIVISOR = 100.0; + + // Constants for discount description strings + private const THREE_FOR_TWO_DESCRIPTION = '3 for 2'; + private const TWO_FOR_PREFIX = '2 for '; + private const FOR_SEPARATOR = ' for '; + private const PERCENT_OFF_SUFFIX = '% off'; + /** * @var ProductQuantity[] */ @@ -25,7 +38,7 @@ public function __construct() public function addItem(Product $product): void { - $this->addItemQuantity($product, 1.0); + $this->addItemQuantity($product, self::DEFAULT_ITEM_QUANTITY); } /** @@ -65,35 +78,35 @@ public function handleOffers(Receipt $receipt, Map $offers, SupermarketCatalog $ $discount = null; $x = 1; if ($offer->getOfferType()->equals(SpecialOfferType::THREE_FOR_TWO())) { - $x = 3; + $x = self::THREE_FOR_TWO_QUANTITY; } elseif ($offer->getOfferType()->equals(SpecialOfferType::TWO_FOR_AMOUNT())) { - $x = 2; - if ($quantityAsInt >= 2) { - $total = $offer->getArgument() * intdiv($quantityAsInt, $x) + $quantityAsInt % 2 * $unitPrice; + $x = self::TWO_FOR_AMOUNT_QUANTITY; + if ($quantityAsInt >= self::TWO_FOR_AMOUNT_QUANTITY) { + $total = $offer->getArgument() * intdiv($quantityAsInt, $x) + $quantityAsInt % self::TWO_FOR_AMOUNT_QUANTITY * $unitPrice; $discountN = $unitPrice * $quantity - $total; - $discount = new Discount($p, "2 for {$offer->getArgument()}", -1 * $discountN); + $discount = new Discount($p, self::TWO_FOR_PREFIX . "{$offer->getArgument()}", -1 * $discountN); } } if ($offer->getOfferType()->equals(SpecialOfferType::FIVE_FOR_AMOUNT())) { - $x = 5; + $x = self::FIVE_FOR_AMOUNT_QUANTITY; } $numberOfXs = intdiv($quantityAsInt, $x); - if ($offer->getOfferType()->equals(SpecialOfferType::THREE_FOR_TWO()) && $quantityAsInt > 2) { - $discountAmount = $quantity * $unitPrice - ($numberOfXs * 2 * $unitPrice + $quantityAsInt % 3 * $unitPrice); - $discount = new Discount($p, '3 for 2', -$discountAmount); + if ($offer->getOfferType()->equals(SpecialOfferType::THREE_FOR_TWO()) && $quantityAsInt > self::TWO_FOR_AMOUNT_QUANTITY) { + $discountAmount = $quantity * $unitPrice - ($numberOfXs * self::TWO_FOR_AMOUNT_QUANTITY * $unitPrice + $quantityAsInt % self::THREE_FOR_TWO_QUANTITY * $unitPrice); + $discount = new Discount($p, self::THREE_FOR_TWO_DESCRIPTION, -$discountAmount); } if ($offer->getOfferType()->equals(SpecialOfferType::TEN_PERCENT_DISCOUNT())) { $discount = new Discount( $p, - "{$offer->getArgument()}% off", - -$quantity * $unitPrice * $offer->getArgument() / 100.0 + "{$offer->getArgument()}" . self::PERCENT_OFF_SUFFIX, + -$quantity * $unitPrice * $offer->getArgument() / self::PERCENTAGE_DIVISOR ); } - if ($offer->getOfferType()->equals(SpecialOfferType::FIVE_FOR_AMOUNT()) && $quantityAsInt >= 5) { - $discountTotal = $unitPrice * $quantity - ($offer->getArgument() * $numberOfXs + $quantityAsInt % 5 * $unitPrice); - $discount = new Discount($p, "${x} for {$offer->getArgument()}", -$discountTotal); + if ($offer->getOfferType()->equals(SpecialOfferType::FIVE_FOR_AMOUNT()) && $quantityAsInt >= self::FIVE_FOR_AMOUNT_QUANTITY) { + $discountTotal = $unitPrice * $quantity - ($offer->getArgument() * $numberOfXs + $quantityAsInt % self::FIVE_FOR_AMOUNT_QUANTITY * $unitPrice); + $discount = new Discount($p, "${x}" . self::FOR_SEPARATOR . "{$offer->getArgument()}", -$discountTotal); } if ($discount !== null) { diff --git a/python/receipt_printer.py b/python/receipt_printer.py index 7550fe9..7e58ab4 100644 --- a/python/receipt_printer.py +++ b/python/receipt_printer.py @@ -1,8 +1,14 @@ from model_objects import ProductUnit +# Constants for magic numbers and strings +DEFAULT_COLUMNS = 40 +SINGLE_ITEM_QUANTITY = 1 +TOTAL_LABEL = "Total: " + + class ReceiptPrinter: - def __init__(self, columns=40): + def __init__(self, columns=DEFAULT_COLUMNS): self.columns = columns def print_receipt(self, receipt): @@ -23,7 +29,7 @@ def print_receipt_item(self, item): total_price_printed = self.print_price(item.total_price) name = item.product.name line = self.format_line_with_whitespace(name, total_price_printed) - if item.quantity != 1: + if item.quantity != SINGLE_ITEM_QUANTITY: line += f" {self.print_price(item.price)} * {self.print_quantity(item)}\n" return line @@ -51,6 +57,6 @@ def print_discount(self, discount): return self.format_line_with_whitespace(name, value) def present_total(self, receipt): - name = "Total: " + name = TOTAL_LABEL value = self.print_price(receipt.total_price()) return self.format_line_with_whitespace(name, value) diff --git a/python/shopping_cart.py b/python/shopping_cart.py index 4e36cc5..d7f3058 100644 --- a/python/shopping_cart.py +++ b/python/shopping_cart.py @@ -2,6 +2,19 @@ from model_objects import ProductQuantity, SpecialOfferType, Discount +# Constants for magic numbers +DEFAULT_ITEM_QUANTITY = 1.0 +TWO_FOR_AMOUNT_QUANTITY = 2 +THREE_FOR_TWO_QUANTITY = 3 +FIVE_FOR_AMOUNT_QUANTITY = 5 +PERCENTAGE_DIVISOR = 100.0 + +# Constants for discount description strings +THREE_FOR_TWO_DESCRIPTION = "3 for 2" +TWO_FOR_PREFIX = "2 for " +FOR_SEPARATOR = " for " +PERCENT_OFF_SUFFIX = "% off" + class ShoppingCart: @@ -14,7 +27,7 @@ def items(self): return self._items def add_item(self, product): - self.add_item_quantity(product, 1.0) + self.add_item_quantity(product, DEFAULT_ITEM_QUANTITY) @property def product_quantities(self): @@ -37,32 +50,32 @@ def handle_offers(self, receipt, offers, catalog): discount = None x = 1 if offer.offer_type == SpecialOfferType.THREE_FOR_TWO: - x = 3 + x = THREE_FOR_TWO_QUANTITY elif offer.offer_type == SpecialOfferType.TWO_FOR_AMOUNT: - x = 2 - if quantity_as_int >= 2: - total = offer.argument * (quantity_as_int / x) + quantity_as_int % 2 * unit_price + x = TWO_FOR_AMOUNT_QUANTITY + if quantity_as_int >= TWO_FOR_AMOUNT_QUANTITY: + total = offer.argument * (quantity_as_int / x) + quantity_as_int % TWO_FOR_AMOUNT_QUANTITY * unit_price discount_n = unit_price * quantity - total - discount = Discount(p, "2 for " + str(offer.argument), -discount_n) + discount = Discount(p, TWO_FOR_PREFIX + str(offer.argument), -discount_n) if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT: - x = 5 + x = FIVE_FOR_AMOUNT_QUANTITY number_of_x = math.floor(quantity_as_int / x) - if offer.offer_type == SpecialOfferType.THREE_FOR_TWO and quantity_as_int > 2: + if offer.offer_type == SpecialOfferType.THREE_FOR_TWO and quantity_as_int > TWO_FOR_AMOUNT_QUANTITY: discount_amount = quantity * unit_price - ( - (number_of_x * 2 * unit_price) + quantity_as_int % 3 * unit_price) - discount = Discount(p, "3 for 2", -discount_amount) + (number_of_x * TWO_FOR_AMOUNT_QUANTITY * unit_price) + quantity_as_int % THREE_FOR_TWO_QUANTITY * unit_price) + discount = Discount(p, THREE_FOR_TWO_DESCRIPTION, -discount_amount) if offer.offer_type == SpecialOfferType.TEN_PERCENT_DISCOUNT: - discount = Discount(p, str(offer.argument) + "% off", - -quantity * unit_price * offer.argument / 100.0) + discount = Discount(p, str(offer.argument) + PERCENT_OFF_SUFFIX, + -quantity * unit_price * offer.argument / PERCENTAGE_DIVISOR) - if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT and quantity_as_int >= 5: + if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT and quantity_as_int >= FIVE_FOR_AMOUNT_QUANTITY: discount_total = unit_price * quantity - ( - offer.argument * number_of_x + quantity_as_int % 5 * unit_price) - discount = Discount(p, str(x) + " for " + str(offer.argument), -discount_total) + offer.argument * number_of_x + quantity_as_int % FIVE_FOR_AMOUNT_QUANTITY * unit_price) + discount = Discount(p, str(x) + FOR_SEPARATOR + str(offer.argument), -discount_total) if discount: receipt.add_discount(discount) diff --git a/python_pytest/src/shopping_cart.py b/python_pytest/src/shopping_cart.py index 4e36cc5..d7f3058 100644 --- a/python_pytest/src/shopping_cart.py +++ b/python_pytest/src/shopping_cart.py @@ -2,6 +2,19 @@ from model_objects import ProductQuantity, SpecialOfferType, Discount +# Constants for magic numbers +DEFAULT_ITEM_QUANTITY = 1.0 +TWO_FOR_AMOUNT_QUANTITY = 2 +THREE_FOR_TWO_QUANTITY = 3 +FIVE_FOR_AMOUNT_QUANTITY = 5 +PERCENTAGE_DIVISOR = 100.0 + +# Constants for discount description strings +THREE_FOR_TWO_DESCRIPTION = "3 for 2" +TWO_FOR_PREFIX = "2 for " +FOR_SEPARATOR = " for " +PERCENT_OFF_SUFFIX = "% off" + class ShoppingCart: @@ -14,7 +27,7 @@ def items(self): return self._items def add_item(self, product): - self.add_item_quantity(product, 1.0) + self.add_item_quantity(product, DEFAULT_ITEM_QUANTITY) @property def product_quantities(self): @@ -37,32 +50,32 @@ def handle_offers(self, receipt, offers, catalog): discount = None x = 1 if offer.offer_type == SpecialOfferType.THREE_FOR_TWO: - x = 3 + x = THREE_FOR_TWO_QUANTITY elif offer.offer_type == SpecialOfferType.TWO_FOR_AMOUNT: - x = 2 - if quantity_as_int >= 2: - total = offer.argument * (quantity_as_int / x) + quantity_as_int % 2 * unit_price + x = TWO_FOR_AMOUNT_QUANTITY + if quantity_as_int >= TWO_FOR_AMOUNT_QUANTITY: + total = offer.argument * (quantity_as_int / x) + quantity_as_int % TWO_FOR_AMOUNT_QUANTITY * unit_price discount_n = unit_price * quantity - total - discount = Discount(p, "2 for " + str(offer.argument), -discount_n) + discount = Discount(p, TWO_FOR_PREFIX + str(offer.argument), -discount_n) if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT: - x = 5 + x = FIVE_FOR_AMOUNT_QUANTITY number_of_x = math.floor(quantity_as_int / x) - if offer.offer_type == SpecialOfferType.THREE_FOR_TWO and quantity_as_int > 2: + if offer.offer_type == SpecialOfferType.THREE_FOR_TWO and quantity_as_int > TWO_FOR_AMOUNT_QUANTITY: discount_amount = quantity * unit_price - ( - (number_of_x * 2 * unit_price) + quantity_as_int % 3 * unit_price) - discount = Discount(p, "3 for 2", -discount_amount) + (number_of_x * TWO_FOR_AMOUNT_QUANTITY * unit_price) + quantity_as_int % THREE_FOR_TWO_QUANTITY * unit_price) + discount = Discount(p, THREE_FOR_TWO_DESCRIPTION, -discount_amount) if offer.offer_type == SpecialOfferType.TEN_PERCENT_DISCOUNT: - discount = Discount(p, str(offer.argument) + "% off", - -quantity * unit_price * offer.argument / 100.0) + discount = Discount(p, str(offer.argument) + PERCENT_OFF_SUFFIX, + -quantity * unit_price * offer.argument / PERCENTAGE_DIVISOR) - if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT and quantity_as_int >= 5: + if offer.offer_type == SpecialOfferType.FIVE_FOR_AMOUNT and quantity_as_int >= FIVE_FOR_AMOUNT_QUANTITY: discount_total = unit_price * quantity - ( - offer.argument * number_of_x + quantity_as_int % 5 * unit_price) - discount = Discount(p, str(x) + " for " + str(offer.argument), -discount_total) + offer.argument * number_of_x + quantity_as_int % FIVE_FOR_AMOUNT_QUANTITY * unit_price) + discount = Discount(p, str(x) + FOR_SEPARATOR + str(offer.argument), -discount_total) if discount: receipt.add_discount(discount) diff --git a/ruby/lib/models/shopping_cart.rb b/ruby/lib/models/shopping_cart.rb index 6eea42e..138169f 100644 --- a/ruby/lib/models/shopping_cart.rb +++ b/ruby/lib/models/shopping_cart.rb @@ -1,3 +1,16 @@ +# Constants for magic numbers +DEFAULT_ITEM_QUANTITY = 1.0 +TWO_FOR_AMOUNT_QUANTITY = 2 +THREE_FOR_TWO_QUANTITY = 3 +FIVE_FOR_AMOUNT_QUANTITY = 5 +PERCENTAGE_DIVISOR = 100.0 + +# Constants for discount description strings +THREE_FOR_TWO_DESCRIPTION = "3 for 2" +TWO_FOR_PREFIX = "2 for " +FOR_SEPARATOR = " for " +PERCENT_OFF_SUFFIX = "% off" + class ShoppingCart def initialize @@ -10,7 +23,7 @@ def items end def add_item(product) - add_item_quantity(product, 1.0) + add_item_quantity(product, DEFAULT_ITEM_QUANTITY) nil end @@ -37,31 +50,31 @@ def handle_offers(receipt, offers, catalog) discount = nil x = 1 if offer.offer_type == SpecialOfferType::THREE_FOR_TWO - x = 3 + x = THREE_FOR_TWO_QUANTITY elsif offer.offer_type == SpecialOfferType::TWO_FOR_AMOUNT - x = 2 - if quantity_as_int >= 2 - total = offer.argument * (quantity_as_int / x) + quantity_as_int % 2 * unit_price + x = TWO_FOR_AMOUNT_QUANTITY + if quantity_as_int >= TWO_FOR_AMOUNT_QUANTITY + total = offer.argument * (quantity_as_int / x) + quantity_as_int % TWO_FOR_AMOUNT_QUANTITY * unit_price discount_n = unit_price * quantity - total - discount = Discount.new(p, "2 for " + offer.argument.to_s, discount_n) + discount = Discount.new(p, TWO_FOR_PREFIX + offer.argument.to_s, discount_n) end end if offer.offer_type == SpecialOfferType:: FIVE_FOR_AMOUNT - x = 5 + x = FIVE_FOR_AMOUNT_QUANTITY end number_of_x = quantity_as_int / x - if offer.offer_type == SpecialOfferType::THREE_FOR_TWO && quantity_as_int > 2 - discount_amount = quantity * unit_price - ((number_of_x * 2 * unit_price) + quantity_as_int % 3 * unit_price) - discount = Discount.new(p, "3 for 2", discount_amount) + if offer.offer_type == SpecialOfferType::THREE_FOR_TWO && quantity_as_int > TWO_FOR_AMOUNT_QUANTITY + discount_amount = quantity * unit_price - ((number_of_x * TWO_FOR_AMOUNT_QUANTITY * unit_price) + quantity_as_int % THREE_FOR_TWO_QUANTITY * unit_price) + discount = Discount.new(p, THREE_FOR_TWO_DESCRIPTION, discount_amount) end if offer.offer_type == SpecialOfferType::TEN_PERCENT_DISCOUNT - discount = Discount.new(p, offer.argument.to_s + "% off", quantity * unit_price * offer.argument / 100.0) + discount = Discount.new(p, offer.argument.to_s + PERCENT_OFF_SUFFIX, quantity * unit_price * offer.argument / PERCENTAGE_DIVISOR) end - if offer.offer_type == SpecialOfferType::FIVE_FOR_AMOUNT && quantity_as_int >= 5 - discount_total = unit_price * quantity - (offer.argument * number_of_x + quantity_as_int % 5 * unit_price) - discount = Discount.new(p, x.to_s + " for " + offer.argument.to_s, discount_total) + if offer.offer_type == SpecialOfferType::FIVE_FOR_AMOUNT && quantity_as_int >= FIVE_FOR_AMOUNT_QUANTITY + discount_total = unit_price * quantity - (offer.argument * number_of_x + quantity_as_int % FIVE_FOR_AMOUNT_QUANTITY * unit_price) + discount = Discount.new(p, x.to_s + FOR_SEPARATOR + offer.argument.to_s, discount_total) end receipt.add_discount(discount) if discount diff --git a/swift/SupermarketReceipt/ShoppingCart.swift b/swift/SupermarketReceipt/ShoppingCart.swift index d34c8bc..6edbc53 100644 --- a/swift/SupermarketReceipt/ShoppingCart.swift +++ b/swift/SupermarketReceipt/ShoppingCart.swift @@ -1,10 +1,23 @@ +// Constants for magic numbers +private let defaultItemQuantity = 1.0 +private let twoForAmountQuantity = 2 +private let threeForTwoQuantity = 3 +private let fiveForAmountQuantity = 5 +private let percentageDivisor = 100.0 + +// Constants for discount description strings +private let threeForTwoDescription = "3 for 2" +private let twoForPrefix = "2 for " +private let forSeparator = " for " +private let percentOffSuffix = "% off" + public class ShoppingCart { public var items = [ProductQuantity]() public var productQuantities = [Product: Double]() func addItem(product: Product) { - self.addItemQuantity(product: product, quantity: 1.0) + self.addItemQuantity(product: product, quantity: defaultItemQuantity) } public func addItemQuantity(product: Product , quantity: Double) { @@ -26,42 +39,42 @@ public class ShoppingCart { var discount: Discount? = nil var x = 1 if offer?.offerType == SpecialOfferType.ThreeForTwo { - x = 3 + x = threeForTwoQuantity } else if offer?.offerType == SpecialOfferType.TwoForAmount { - x = 2 - if (quantityAsInt >= 2) { + x = twoForAmountQuantity + if (quantityAsInt >= twoForAmountQuantity) { var intDivision = quantityAsInt / x var pricePerUnit = (offer!.argument * Double(intDivision)) - var theTotal = Double(quantityAsInt % 2) * unitPrice + var theTotal = Double(quantityAsInt % twoForAmountQuantity) * unitPrice var total = pricePerUnit + theTotal var discountN = unitPrice * quantity! - total - discount = Discount(description: "2 for \(offer!.argument)", discountAmount: discountN, product: p) + discount = Discount(description: twoForPrefix + "\(offer!.argument)", discountAmount: discountN, product: p) } } else if offer?.offerType == SpecialOfferType.FiveForAmount { - x = 5 + x = fiveForAmountQuantity } var numberOfXs = quantityAsInt / x - if offer?.offerType == SpecialOfferType.ThreeForTwo && quantityAsInt > 2 { - var left = Double(numberOfXs * 2) * unitPrice - var right = Double(quantityAsInt % 3) * unitPrice + if offer?.offerType == SpecialOfferType.ThreeForTwo && quantityAsInt > twoForAmountQuantity { + var left = Double(numberOfXs * twoForAmountQuantity) * unitPrice + var right = Double(quantityAsInt % threeForTwoQuantity) * unitPrice var lastPart = left + right var discountAmount = ((quantity ?? 1) * unitPrice) - lastPart - discount = Discount(description: "3 for 2", discountAmount: discountAmount, product: p) + discount = Discount(description: threeForTwoDescription, discountAmount: discountAmount, product: p) } if offer?.offerType == SpecialOfferType.TenPercentDiscount { - discount = Discount(description: "\(offer!.argument)% off", discountAmount: (quantity ?? 1) * unitPrice * (offer?.argument ?? 1) / 100.0, product: p) + discount = Discount(description: "\(offer!.argument)" + percentOffSuffix, discountAmount: (quantity ?? 1) * unitPrice * (offer?.argument ?? 1) / percentageDivisor, product: p) } - if offer?.offerType == SpecialOfferType.FiveForAmount && quantityAsInt >= 5 { + if offer?.offerType == SpecialOfferType.FiveForAmount && quantityAsInt >= fiveForAmountQuantity { var left = (unitPrice * (quantity ?? 1)) - var right = ((offer?.argument ?? 1) * Double(numberOfXs)) + (Double(quantityAsInt % 5) * unitPrice) + var right = ((offer?.argument ?? 1) * Double(numberOfXs)) + (Double(quantityAsInt % fiveForAmountQuantity) * unitPrice) var discountTotal = left - right - discount = Discount(description: "\(x) for \(offer!.argument)", discountAmount: discountTotal, product: p) + discount = Discount(description: "\(x)" + forSeparator + "\(offer!.argument)", discountAmount: discountTotal, product: p) } if discount != nil { receipt.addDiscount(discount: discount!) diff --git a/typescript/src/model/ShoppingCart.ts b/typescript/src/model/ShoppingCart.ts index ac090cc..9a979bd 100644 --- a/typescript/src/model/ShoppingCart.ts +++ b/typescript/src/model/ShoppingCart.ts @@ -7,6 +7,19 @@ import {Receipt} from "./Receipt" import {Offer} from "./Offer" import {SpecialOfferType} from "./SpecialOfferType" +// Constants for magic numbers +const DEFAULT_ITEM_QUANTITY = 1.0; +const TWO_FOR_AMOUNT_QUANTITY = 2; +const THREE_FOR_TWO_QUANTITY = 3; +const FIVE_FOR_AMOUNT_QUANTITY = 5; +const PERCENTAGE_DIVISOR = 100.0; + +// Constants for discount description strings +const THREE_FOR_TWO_DESCRIPTION = "3 for 2"; +const TWO_FOR_PREFIX = "2 for "; +const FOR_SEPARATOR = " for "; +const PERCENT_OFF_SUFFIX = "% off"; + type ProductQuantities = { [productName: string]: ProductQuantity } export type OffersByProduct = {[productName: string]: Offer}; @@ -21,7 +34,7 @@ export class ShoppingCart { } addItem(product: Product): void { - this.addItemQuantity(product, 1.0); + this.addItemQuantity(product, DEFAULT_ITEM_QUANTITY); } productQuantities(): ProductQuantities { @@ -57,30 +70,30 @@ export class ShoppingCart { let discount : Discount|null = null; let x = 1; if (offer.offerType == SpecialOfferType.ThreeForTwo) { - x = 3; + x = THREE_FOR_TWO_QUANTITY; } else if (offer.offerType == SpecialOfferType.TwoForAmount) { - x = 2; - if (quantityAsInt >= 2) { - const total = offer.argument * Math.floor(quantityAsInt / x) + quantityAsInt % 2 * unitPrice; + x = TWO_FOR_AMOUNT_QUANTITY; + if (quantityAsInt >= TWO_FOR_AMOUNT_QUANTITY) { + const total = offer.argument * Math.floor(quantityAsInt / x) + quantityAsInt % TWO_FOR_AMOUNT_QUANTITY * unitPrice; const discountN = unitPrice * quantity - total; - discount = new Discount(product, "2 for " + offer.argument, discountN); + discount = new Discount(product, TWO_FOR_PREFIX + offer.argument, discountN); } } if (offer.offerType == SpecialOfferType.FiveForAmount) { - x = 5; + x = FIVE_FOR_AMOUNT_QUANTITY; } const numberOfXs = Math.floor(quantityAsInt / x); - if (offer.offerType == SpecialOfferType.ThreeForTwo && quantityAsInt > 2) { - const discountAmount = quantity * unitPrice - ((numberOfXs * 2 * unitPrice) + quantityAsInt % 3 * unitPrice); - discount = new Discount(product, "3 for 2", discountAmount); + if (offer.offerType == SpecialOfferType.ThreeForTwo && quantityAsInt > TWO_FOR_AMOUNT_QUANTITY) { + const discountAmount = quantity * unitPrice - ((numberOfXs * TWO_FOR_AMOUNT_QUANTITY * unitPrice) + quantityAsInt % THREE_FOR_TWO_QUANTITY * unitPrice); + discount = new Discount(product, THREE_FOR_TWO_DESCRIPTION, discountAmount); } if (offer.offerType == SpecialOfferType.TenPercentDiscount) { - discount = new Discount(product, offer.argument + "% off", quantity * unitPrice * offer.argument / 100.0); + discount = new Discount(product, offer.argument + PERCENT_OFF_SUFFIX, quantity * unitPrice * offer.argument / PERCENTAGE_DIVISOR); } - if (offer.offerType == SpecialOfferType.FiveForAmount && quantityAsInt >= 5) { - const discountTotal = unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % 5 * unitPrice); - discount = new Discount(product, x + " for " + offer.argument, discountTotal); + if (offer.offerType == SpecialOfferType.FiveForAmount && quantityAsInt >= FIVE_FOR_AMOUNT_QUANTITY) { + const discountTotal = unitPrice * quantity - (offer.argument * numberOfXs + quantityAsInt % FIVE_FOR_AMOUNT_QUANTITY * unitPrice); + discount = new Discount(product, x + FOR_SEPARATOR + offer.argument, discountTotal); } if (discount != null) receipt.addDiscount(discount);