diff --git a/c/src/printer.c b/c/src/printer.c index ddfedd2..40d51ea 100644 --- a/c/src/printer.c +++ b/c/src/printer.c @@ -2,6 +2,24 @@ #include #include "printer.h" +// Constants for receipt formatting +#define QUANTITY_THRESHOLD 1 +#define PRICE_DECIMAL_PLACES 2 +#define WEIGHT_DECIMAL_PLACES 3 +#define INTEGER_DECIMAL_PLACES 0 + +// Format strings +#define PRICE_FORMAT "%.2f" +#define WEIGHT_FORMAT "%.3f" +#define INTEGER_FORMAT "%.0f" + +// Display strings +#define UNIT_PRICE_INDENT " " +#define MULTIPLICATION_SYMBOL "*" +#define TOTAL_LABEL "Total:" +#define WHITESPACE_CHAR ' ' +#define NEWLINE "\n" + void printReceiptItem(char *buffer, struct receipt_item_t *item); void printDiscount(char *buffer, struct discount_t *pDiscount); @@ -15,7 +33,7 @@ void print_receipt(char* buffer, struct receipt_t* receipt) { for (int i = 0; i < receipt->discountCount; ++i) { printDiscount(buffer, &receipt->discounts[i]); } - sprintf(buffer + strlen(buffer), "\n"); + sprintf(buffer + strlen(buffer), NEWLINE); print_total(buffer, receipt); @@ -23,7 +41,7 @@ void print_receipt(char* buffer, struct receipt_t* receipt) { void printReceiptItem(char *buffer, struct receipt_item_t *item) { char price[MAX_NAME_LENGTH]; - sprintf(price, "%.2f", (*item).totalPrice); + sprintf(price, PRICE_FORMAT, (*item).totalPrice); char name[MAX_NAME_LENGTH]; sprintf(name, "%s", (*item).product->name); @@ -31,22 +49,22 @@ void printReceiptItem(char *buffer, struct receipt_item_t *item) { print_line(line, name, price); sprintf(buffer + strlen(buffer), "%s", line); - if (item->quantity != 1) { + if (item->quantity != QUANTITY_THRESHOLD) { char line2[LINE_LENGTH]; if (item->product->unit == Each) { - sprintf(line2, " %.2f*%.0f", item->price, item->quantity); + sprintf(line2, UNIT_PRICE_INDENT PRICE_FORMAT MULTIPLICATION_SYMBOL INTEGER_FORMAT, item->price, item->quantity); } else { - sprintf(line2, " %.2f*%.3f", item->price, item->quantity); + sprintf(line2, UNIT_PRICE_INDENT PRICE_FORMAT MULTIPLICATION_SYMBOL WEIGHT_FORMAT, item->price, item->quantity); } - sprintf(buffer + strlen(buffer), "%s\n", line2); + sprintf(buffer + strlen(buffer), "%s" NEWLINE, line2); } } void print_total(char *buffer, struct receipt_t *receipt) { char total[MAX_NAME_LENGTH]; - sprintf(total, "%0.2f", total_price(receipt)); + sprintf(total, PRICE_FORMAT, total_price(receipt)); char total_line[LINE_LENGTH]; - print_line(total_line, "Total:", total); + print_line(total_line, TOTAL_LABEL, total); sprintf(buffer + strlen(buffer), "%s", total_line); } @@ -54,7 +72,7 @@ void printDiscount(char *buffer, struct discount_t *discount) { char name[LINE_LENGTH]; sprintf(name, "%s(%s)", discount->description, discount->product->name); char price[MAX_NAME_LENGTH]; - sprintf(price, "%.2f", discount->amount); + sprintf(price, PRICE_FORMAT, discount->amount); char line[LINE_LENGTH]; print_line(line, name, price); @@ -67,9 +85,9 @@ print_line(char *buffer, const char *key, const char *value) { int whitespace_length = LINE_LENGTH - strlen(key) - strlen(value); char whitespace[whitespace_length]; for (int i = 0; i < whitespace_length -1; ++i) { - whitespace[i] = ' '; + whitespace[i] = WHITESPACE_CHAR; } whitespace[whitespace_length-1] = '\0'; - sprintf(buffer, "%s%s%s\n", key, whitespace, value ); + sprintf(buffer, "%s%s%s" NEWLINE, key, whitespace, value ); } diff --git a/c/src/supermarket.c b/c/src/supermarket.c index 414ce1d..749821f 100644 --- a/c/src/supermarket.c +++ b/c/src/supermarket.c @@ -3,6 +3,15 @@ #include #include "supermarket.h" +// Constants for special offers +#define THREE_FOR_TWO_QUANTITY 3 +#define TWO_FOR_AMOUNT_QUANTITY 2 +#define FIVE_FOR_AMOUNT_QUANTITY 5 +#define TWO_FOR_AMOUNT_MIN_QUANTITY 2 +#define THREE_FOR_TWO_MIN_QUANTITY 2 +#define FIVE_FOR_AMOUNT_MIN_QUANTITY 5 +#define PERCENT_DIVISOR 100.0 + 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,34 +85,34 @@ 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_MIN_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, "%d for %f", TWO_FOR_AMOUNT_QUANTITY, 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 > THREE_FOR_TWO_MIN_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, "%d for %d", THREE_FOR_TWO_QUANTITY, TWO_FOR_AMOUNT_QUANTITY); 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); + discount = discount_create(description, -quantity * unitPrice * offer->argument / PERCENT_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_MIN_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); discount = discount_create(description, -discountTotal, &product); diff --git a/common-lisp/source/receipt-printer.lisp b/common-lisp/source/receipt-printer.lisp index b541373..3d27127 100644 --- a/common-lisp/source/receipt-printer.lisp +++ b/common-lisp/source/receipt-printer.lisp @@ -2,9 +2,21 @@ (in-package :supermarket-receipt) +;; Constants for receipt formatting +(defconstant +default-columns+ 40) +(defconstant +quantity-threshold+ 1.0) +(defconstant +price-decimal-places+ 2) +(defconstant +weight-decimal-places+ 3) + +;; Display strings +(defconstant +unit-price-indent+ " ") +(defconstant +multiplication-symbol+ " * ") +(defconstant +total-label+ "Total: ") +(defconstant +whitespace-char+ " ") + (defclass receipt-printer () ((columns :initarg :columns - :initform 40 + :initform +default-columns+ :type integer :accessor printer-columns))) @@ -22,9 +34,11 @@ (let* ((total-price-printed (print-price a-printer (item-total-price an-item))) (name (product-name (item-product an-item))) (line (format-line-with-whitespace a-printer name total-price-printed))) - (unless (= 1.0 (item-quantity an-item)) - (setf line (format nil "~A ~A * ~A~%" line + (unless (= +quantity-threshold+ (item-quantity an-item)) + (setf line (format nil "~A~A~A~A~A~%" line + +unit-price-indent+ (print-price a-printer (item-price an-item)) + +multiplication-symbol+ (print-quantity a-printer an-item)))) line)) @@ -32,18 +46,18 @@ (let* ((line a-name) (whitespace-size (- (printer-columns a-printer) (length a-name) (length a-value)))) (loop for index from 1 to whitespace-size do - (setf line (concatenate 'string line " "))) + (setf line (concatenate 'string line +whitespace-char+))) (setf line (concatenate 'string line a-value)) (setf line (format nil "~A~%" line)) line)) (defmethod print-price ((a-printer receipt-printer) (a-price single-float)) - (format nil "~2$" a-price)) + (format nil (format nil "~~~d$" +price-decimal-places+) a-price)) (defmethod print-quantity ((a-printer receipt-printer) (an-item receipt-item)) (if (eq 'each (the-product-unit (item-product an-item))) (format nil "~d" (floor (item-quantity an-item))) - (format nil "~3$" (item-quantity an-item)))) + (format nil (format nil "~~~d$" +weight-decimal-places+) (item-quantity an-item)))) (defmethod print-discount ((a-printer receipt-printer) (a-discount discount)) (let ((name (format nil "~A(~A)" (discount-description a-discount) (product-name (discounted-product a-discount)))) @@ -51,6 +65,6 @@ (format-line-with-whitespace a-printer name value))) (defmethod present-total ((a-printer receipt-printer) (a-receipt receipt)) - (let ((name "Total: ") + (let ((name +total-label+) (value (print-price a-printer (total-price a-receipt)))) (format-line-with-whitespace a-printer name value))) diff --git a/csharp/SupermarketReceipt.Test/ReceiptPrinter.cs b/csharp/SupermarketReceipt.Test/ReceiptPrinter.cs index 954f9d7..a8ff8c7 100644 --- a/csharp/SupermarketReceipt.Test/ReceiptPrinter.cs +++ b/csharp/SupermarketReceipt.Test/ReceiptPrinter.cs @@ -5,6 +5,19 @@ namespace SupermarketReceipt { public class ReceiptPrinter { + // Constants for receipt formatting + private const int DefaultColumns = 40; + private const int QuantityThreshold = 1; + private const int PriceDecimalPlaces = 2; + private const int WeightDecimalPlaces = 3; + + // Display strings + private const string UnitPriceIndent = " "; + private const string MultiplicationSymbol = " * "; + private const string TotalLabel = "Total: "; + private const string WhitespaceChar = " "; + private const string Newline = "\n"; + private static readonly CultureInfo Culture = CultureInfo.CreateSpecificCulture("en-GB"); private readonly int _columns; @@ -15,7 +28,7 @@ public ReceiptPrinter(int columns) _columns = columns; } - public ReceiptPrinter() : this(40) + public ReceiptPrinter() : this(DefaultColumns) { } @@ -36,7 +49,7 @@ public string PrintReceipt(Receipt receipt) } { - result.Append("\n"); + result.Append(Newline); result.Append(PrintTotal(receipt)); } return result.ToString(); @@ -44,7 +57,7 @@ public string PrintReceipt(Receipt receipt) private string PrintTotal(Receipt receipt) { - string name = "Total: "; + string name = TotalLabel; string value = PrintPrice(receipt.GetTotalPrice()); return FormatLineWithWhitespace(name, value); } @@ -62,9 +75,9 @@ private string PrintReceiptItem(ReceiptItem item) string totalPrice = PrintPrice(item.TotalPrice); string name = item.Product.Name; string line = FormatLineWithWhitespace(name, totalPrice); - if (item.Quantity != 1) + if (item.Quantity != QuantityThreshold) { - line += " " + PrintPrice(item.Price) + " * " + PrintQuantity(item) + "\n"; + line += UnitPriceIndent + PrintPrice(item.Price) + MultiplicationSymbol + PrintQuantity(item) + Newline; } return line; @@ -77,23 +90,23 @@ private string FormatLineWithWhitespace(string name, string value) line.Append(name); int whitespaceSize = this._columns - name.Length - value.Length; for (int i = 0; i < whitespaceSize; i++) { - line.Append(" "); + line.Append(WhitespaceChar); } line.Append(value); - line.Append('\n'); + line.Append(Newline); return line.ToString(); } private string PrintPrice(double price) { - return price.ToString("N2", Culture); + return price.ToString("N" + PriceDecimalPlaces, Culture); } private static string PrintQuantity(ReceiptItem item) { return ProductUnit.Each == item.Product.Unit ? ((int) item.Quantity).ToString() - : item.Quantity.ToString("N3", Culture); + : item.Quantity.ToString("N" + WeightDecimalPlaces, Culture); } } diff --git a/go/go.mod b/go/go.mod index fde60a8..2f9ca16 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,3 +6,10 @@ require ( github.com/approvals/go-approval-tests v0.0.0-20220530063708-32d5677069bd golang.org/x/text v0.4.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go/go.sum b/go/go.sum index 8754360..d010154 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,4 +1,13 @@ github.com/approvals/go-approval-tests v0.0.0-20220530063708-32d5677069bd h1:8j7sBEy0h6+Bvr0AeKHIHCsmzCzWGXAQweA7k+uiRYk= github.com/approvals/go-approval-tests v0.0.0-20220530063708-32d5677069bd/go.mod h1:PJOqSY8IofNv3heAD6k8E7EfFS6okiSS9bSAasaAUME= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/supermarket/printers.go b/go/supermarket/printers.go index e742392..e68d624 100644 --- a/go/supermarket/printers.go +++ b/go/supermarket/printers.go @@ -7,6 +7,21 @@ import ( "strings" ) +// Constants for receipt formatting +const ( + defaultColumns = 40 + quantityThreshold = 1 + priceDecimalPlaces = 2 + weightDecimalPlaces = 3 + unitPriceIndent = " " + multiplicationSymbol = " * " + totalLabel = "Total: " + whitespaceChar = " " + newline = "\n" + priceFormat = "%.2f" + weightFormat = "%.3f" +) + type ReceiptPrinter struct { columns int lp *message.Printer @@ -14,7 +29,7 @@ type ReceiptPrinter struct { func NewReceiptPrinter() *ReceiptPrinter { var p ReceiptPrinter - p.columns = 40 + p.columns = defaultColumns p.lp = message.NewPrinter(language.BritishEnglish) return &p } @@ -28,7 +43,7 @@ func (p ReceiptPrinter) printReceipt(receipt *Receipt) string { for _, discount := range receipt.sortedDiscounts() { result += p.presentDiscount(discount) } - result += "\n" + result += newline result += p.presentTotal(receipt) return result @@ -37,8 +52,8 @@ func (p ReceiptPrinter) printReceipt(receipt *Receipt) string { func (p ReceiptPrinter) presentReceiptItem(item ReceiptItem) string { var totalPricePresentation string = p.presentPrice(item.totalPrice) var line = p.formatLineWithWhitespace(item.product.name, totalPricePresentation) - if item.quantity != 1 { - line += fmt.Sprintf(" %s * %s\n", p.presentPrice(item.price), p.presentQuantity(item)) + if item.quantity != quantityThreshold { + line += fmt.Sprintf("%s%s%s%s%s", unitPriceIndent, p.presentPrice(item.price), multiplicationSymbol, p.presentQuantity(item), newline) } return line } @@ -48,14 +63,14 @@ func (p ReceiptPrinter) formatLineWithWhitespace(name string, value string) stri fmt.Fprint(&result, name) var whitespaceSize = p.columns - len(name) - len(value) for i := 1; i <= whitespaceSize; i++ { - fmt.Fprint(&result, " ") + fmt.Fprint(&result, whitespaceChar) } - fmt.Fprintf(&result, "%s\n", value) + fmt.Fprintf(&result, "%s%s", value, newline) return result.String() } func (p ReceiptPrinter) presentPrice(price float64) string { - return p.lp.Sprintf("%.2f", price) + return p.lp.Sprintf(priceFormat, price) } func (p ReceiptPrinter) presentQuantity(item ReceiptItem) string { @@ -63,13 +78,13 @@ func (p ReceiptPrinter) presentQuantity(item ReceiptItem) string { if Each == item.product.unit { result = fmt.Sprintf("%d", int(item.quantity)) } else { - result = p.lp.Sprintf("%.3f", item.quantity) + result = p.lp.Sprintf(weightFormat, item.quantity) } return result } func (p ReceiptPrinter) presentTotal(receipt *Receipt) string { - var name = "Total: " + var name = totalLabel var value = p.presentPrice(receipt.totalPrice()) return p.formatLineWithWhitespace(name, value) } diff --git a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java b/java/src/test/java/dojo/supermarket/ReceiptPrinter.java index 070a13f..ccbe63f 100644 --- a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java +++ b/java/src/test/java/dojo/supermarket/ReceiptPrinter.java @@ -5,11 +5,23 @@ import java.util.Locale; public class ReceiptPrinter { + // Constants for receipt formatting + private static final int DEFAULT_COLUMNS = 40; + private static final int QUANTITY_THRESHOLD = 1; + private static final int PRICE_DECIMAL_PLACES = 2; + private static final int WEIGHT_DECIMAL_PLACES = 3; + + // Display strings + private static final String UNIT_PRICE_INDENT = " "; + private static final String MULTIPLICATION_SYMBOL = " * "; + private static final String TOTAL_LABEL = "Total: "; + private static final String WHITESPACE_CHAR = " "; + private static final String NEWLINE = "\n"; private final int columns; public ReceiptPrinter() { - this(40); + this(DEFAULT_COLUMNS); } public ReceiptPrinter(int columns) { @@ -27,7 +39,7 @@ public String printReceipt(Receipt receipt) { result.append(discountPresentation); } - result.append("\n"); + result.append(NEWLINE); result.append(presentTotal(receipt)); return result.toString(); } @@ -38,8 +50,8 @@ private String presentReceiptItem(ReceiptItem item) { String line = formatLineWithWhitespace(name, totalPricePresentation); - if (item.getQuantity() != 1) { - line += " " + presentPrice(item.getPrice()) + " * " + presentQuantity(item) + "\n"; + if (item.getQuantity() != QUANTITY_THRESHOLD) { + line += UNIT_PRICE_INDENT + presentPrice(item.getPrice()) + MULTIPLICATION_SYMBOL + presentQuantity(item) + NEWLINE; } return line; } @@ -52,7 +64,7 @@ private String presentDiscount(Discount discount) { } private String presentTotal(Receipt receipt) { - String name = "Total: "; + String name = TOTAL_LABEL; String value = presentPrice(receipt.getTotalPrice()); return formatLineWithWhitespace(name, value); } @@ -62,20 +74,20 @@ private String formatLineWithWhitespace(String name, String value) { line.append(name); int whitespaceSize = this.columns - name.length() - value.length(); for (int i = 0; i < whitespaceSize; i++) { - line.append(" "); + line.append(WHITESPACE_CHAR); } line.append(value); - line.append('\n'); + line.append(NEWLINE); return line.toString(); } private static String presentPrice(double price) { - return String.format(Locale.UK, "%.2f", price); + return String.format(Locale.UK, "%." + PRICE_DECIMAL_PLACES + "f", price); } private static String presentQuantity(ReceiptItem item) { return ProductUnit.EACH.equals(item.getProduct().getUnit()) ? String.format("%d", (int)item.getQuantity()) - : String.format(Locale.UK, "%.3f", item.getQuantity()); + : String.format(Locale.UK, "%." + WEIGHT_DECIMAL_PLACES + "f", item.getQuantity()); } } diff --git a/kotlin/src/main/kotlin/supermarket/ReceiptPrinter.kt b/kotlin/src/main/kotlin/supermarket/ReceiptPrinter.kt index 004097f..49b1b58 100644 --- a/kotlin/src/main/kotlin/supermarket/ReceiptPrinter.kt +++ b/kotlin/src/main/kotlin/supermarket/ReceiptPrinter.kt @@ -5,40 +5,58 @@ import supermarket.model.Receipt import supermarket.model.ReceiptItem import java.util.* -class ReceiptPrinter @JvmOverloads constructor(private val columns: Int = 40) { +class ReceiptPrinter @JvmOverloads constructor(private val columns: Int = DEFAULT_COLUMNS) { + companion object { + // Constants for receipt formatting + private const val DEFAULT_COLUMNS = 40 + private const val QUANTITY_THRESHOLD = 1.0 + private const val PRICE_DECIMAL_PLACES = 2 + private const val WEIGHT_DECIMAL_PLACES = 3 + private const val DISCOUNT_LINE_OFFSET = 3 + + // Display strings + private const val UNIT_PRICE_INDENT = " " + private const val MULTIPLICATION_SYMBOL = " * " + private const val TOTAL_LABEL = "Total: " + private const val WHITESPACE_CHAR = " " + private const val NEWLINE = "\n" + private const val DISCOUNT_OPEN_PAREN = "(" + private const val DISCOUNT_CLOSE_PAREN = ")" + private const val DISCOUNT_PREFIX = "-" + } fun printReceipt(receipt: Receipt): String { val result = StringBuilder() for (item in receipt.getItems()) { - val price = String.format(Locale.UK, "%.2f", item.totalPrice) + val price = String.format(Locale.UK, "%.$PRICE_DECIMAL_PLACES" + "f", item.totalPrice) val quantity = presentQuantity(item) val name = item.product.name - val unitPrice = String.format(Locale.UK, "%.2f", item.price) + val unitPrice = String.format(Locale.UK, "%.$PRICE_DECIMAL_PLACES" + "f", item.price) val whitespaceSize = this.columns - name.length - price.length - var line = name + getWhitespace(whitespaceSize) + price + "\n" + var line = name + getWhitespace(whitespaceSize) + price + NEWLINE - if (item.quantity != 1.0) { - line += " $unitPrice * $quantity\n" + if (item.quantity != QUANTITY_THRESHOLD) { + line += "$UNIT_PRICE_INDENT$unitPrice$MULTIPLICATION_SYMBOL$quantity$NEWLINE" } result.append(line) } for (discount in receipt.getDiscounts()) { val productPresentation = discount.product.name - val pricePresentation = String.format(Locale.UK, "%.2f", discount.discountAmount) + val pricePresentation = String.format(Locale.UK, "%.$PRICE_DECIMAL_PLACES" + "f", discount.discountAmount) val description = discount.description result.append(description) - result.append("(") + result.append(DISCOUNT_OPEN_PAREN) result.append(productPresentation) - result.append(")") - result.append(getWhitespace(this.columns - 3 - productPresentation.length - description.length - pricePresentation.length)) - result.append("-") + result.append(DISCOUNT_CLOSE_PAREN) + result.append(getWhitespace(this.columns - DISCOUNT_LINE_OFFSET - productPresentation.length - description.length - pricePresentation.length)) + result.append(DISCOUNT_PREFIX) result.append(pricePresentation) - result.append("\n") + result.append(NEWLINE) } - result.append("\n") - val pricePresentation = String.format(Locale.UK, "%.2f", receipt.totalPrice as Double) - val total = "Total: " + result.append(NEWLINE) + val pricePresentation = String.format(Locale.UK, "%.$PRICE_DECIMAL_PLACES" + "f", receipt.totalPrice as Double) + val total = TOTAL_LABEL val whitespace = getWhitespace(this.columns - total.length - pricePresentation.length) result.append(total).append(whitespace).append(pricePresentation) return result.toString() @@ -46,15 +64,15 @@ class ReceiptPrinter @JvmOverloads constructor(private val columns: Int = 40) { private fun presentQuantity(item: ReceiptItem): String { return if (ProductUnit.Each.equals(item.product.unit)) - String.format("%x", item.quantity.toInt()) + String.format("%d", item.quantity.toInt()) else - String.format(Locale.UK, "%.3f", item.quantity) + String.format(Locale.UK, "%.$WEIGHT_DECIMAL_PLACES" + "f", item.quantity) } private fun getWhitespace(whitespaceSize: Int): String { val whitespace = StringBuilder() for (i in 0 until whitespaceSize) { - whitespace.append(" ") + whitespace.append(WHITESPACE_CHAR) } return whitespace.toString() } diff --git a/php/src/ReceiptPrinter.php b/php/src/ReceiptPrinter.php index 2aca856..f804e9e 100644 --- a/php/src/ReceiptPrinter.php +++ b/php/src/ReceiptPrinter.php @@ -11,8 +11,26 @@ class ReceiptPrinter { + // Constants for receipt formatting + private const DEFAULT_COLUMNS = 40; + private const QUANTITY_THRESHOLD = 1.0; + private const PRICE_DECIMAL_PLACES = 2; + private const WEIGHT_DECIMAL_PLACES = 3; + + // Format strings + private const PRICE_FORMAT = '%.2F'; + private const WEIGHT_FORMAT = '%.3F'; + private const INTEGER_FORMAT = '%d'; + + // Display strings + private const UNIT_PRICE_INDENT = ' '; + private const MULTIPLICATION_SYMBOL = ' * '; + private const TOTAL_LABEL = 'Total: '; + private const WHITESPACE_CHAR = ' '; + private const NEWLINE = "\n"; + public function __construct( - private int $columns = 40 + private int $columns = self::DEFAULT_COLUMNS ) { } @@ -29,7 +47,7 @@ public function printReceipt(Receipt $receipt): string $result .= $discountPresentation; } - $result .= "\n"; + $result .= self::NEWLINE; $result .= $this->presentTotal($receipt); return $result; } @@ -39,10 +57,10 @@ protected function presentReceiptItem(ReceiptItem $item): string $price = self::presentPrice($item->getTotalPrice()); $name = $item->getProduct()->getName(); - $line = $this->formatLineWithWhitespace($name, $price) . "\n"; + $line = $this->formatLineWithWhitespace($name, $price) . self::NEWLINE; - if ($item->getQuantity() !== 1.0) { - $line .= ' ' . self::presentPrice($item->getPrice()) . ' * ' . self::presentQuantity($item) . "\n"; + if ($item->getQuantity() !== self::QUANTITY_THRESHOLD) { + $line .= self::UNIT_PRICE_INDENT . self::presentPrice($item->getPrice()) . self::MULTIPLICATION_SYMBOL . self::presentQuantity($item) . self::NEWLINE; } return $line; } @@ -52,12 +70,12 @@ protected function presentDiscount(Discount $discount): string $name = "{$discount->getDescription()}({$discount->getProduct()->getName()})"; $value = self::presentPrice($discount->getDiscountAmount()); - return $this->formatLineWithWhitespace($name, $value) . "\n"; + return $this->formatLineWithWhitespace($name, $value) . self::NEWLINE; } protected function presentTotal(Receipt $receipt): string { - $name = 'Total: '; + $name = self::TOTAL_LABEL; $value = self::presentPrice($receipt->getTotalPrice()); return $this->formatLineWithWhitespace($name, $value); } @@ -65,18 +83,18 @@ protected function presentTotal(Receipt $receipt): string protected function formatLineWithWhitespace(string $name, string $value): string { $whitespaceSize = $this->columns - strlen($name) - strlen($value); - return $name . str_repeat(' ', $whitespaceSize) . $value; + return $name . str_repeat(self::WHITESPACE_CHAR, $whitespaceSize) . $value; } protected static function presentPrice(float $price): string { - return sprintf('%.2F', $price); + return sprintf(self::PRICE_FORMAT, $price); } private static function presentQuantity(ReceiptItem $item): string { return $item->getProduct()->getUnit()->equals(ProductUnit::EACH()) ? - sprintf('%x', $item->getQuantity()) : - sprintf('%.3F', $item->getQuantity()); + sprintf(self::INTEGER_FORMAT, $item->getQuantity()) : + sprintf(self::WEIGHT_FORMAT, $item->getQuantity()); } } diff --git a/python/receipt_printer.py b/python/receipt_printer.py index 7550fe9..859a47e 100644 --- a/python/receipt_printer.py +++ b/python/receipt_printer.py @@ -1,8 +1,28 @@ from model_objects import ProductUnit class ReceiptPrinter: + # Constants for receipt formatting + DEFAULT_COLUMNS = 40 + QUANTITY_THRESHOLD = 1 + PRICE_DECIMAL_PLACES = 2 + WEIGHT_DECIMAL_PLACES = 3 + DISCOUNT_LINE_OFFSET = 3 + + # Format strings + PRICE_FORMAT = "%.2f" + WEIGHT_FORMAT = "%.3f" + + # Display strings + UNIT_PRICE_INDENT = " " + MULTIPLICATION_SYMBOL = " * " + TOTAL_LABEL = "Total: " + WHITESPACE_CHAR = " " + NEWLINE = "\n" + DISCOUNT_OPEN_PAREN = "(" + DISCOUNT_CLOSE_PAREN = ")" + DISCOUNT_PREFIX = "-" - def __init__(self, columns=40): + def __init__(self, columns=DEFAULT_COLUMNS): self.columns = columns def print_receipt(self, receipt): @@ -15,7 +35,7 @@ def print_receipt(self, receipt): discount_presentation = self.print_discount(discount) result += discount_presentation - result += "\n" + result += self.NEWLINE result += self.present_total(receipt) return str(result) @@ -23,34 +43,34 @@ 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: - line += f" {self.print_price(item.price)} * {self.print_quantity(item)}\n" + if item.quantity != self.QUANTITY_THRESHOLD: + line += f"{self.UNIT_PRICE_INDENT}{self.print_price(item.price)}{self.MULTIPLICATION_SYMBOL}{self.print_quantity(item)}{self.NEWLINE}" return line def format_line_with_whitespace(self, name, value): line = name whitespace_size = self.columns - len(name) - len(value) for i in range(whitespace_size): - line += " " + line += self.WHITESPACE_CHAR line += value - line += "\n" + line += self.NEWLINE return line def print_price(self, price): - return "%.2f" % price + return self.PRICE_FORMAT % price def print_quantity(self, item): if ProductUnit.EACH == item.product.unit: - return str(item.quantity) + return str(int(item.quantity)) else: - return '%.3f' % item.quantity + return self.WEIGHT_FORMAT % item.quantity def print_discount(self, discount): - name = f"{discount.description} ({discount.product.name})" + name = f"{discount.description}{self.WHITESPACE_CHAR}{self.DISCOUNT_OPEN_PAREN}{discount.product.name}{self.DISCOUNT_CLOSE_PAREN}" value = self.print_price(discount.discount_amount) return self.format_line_with_whitespace(name, value) def present_total(self, receipt): - name = "Total: " + name = self.TOTAL_LABEL value = self.print_price(receipt.total_price()) return self.format_line_with_whitespace(name, value) diff --git a/ruby/lib/receipt_printer.rb b/ruby/lib/receipt_printer.rb index 3c06478..d5a38aa 100644 --- a/ruby/lib/receipt_printer.rb +++ b/ruby/lib/receipt_printer.rb @@ -1,55 +1,75 @@ class ReceiptPrinter + # Constants for receipt formatting + DEFAULT_COLUMNS = 40 + QUANTITY_THRESHOLD = 1 + PRICE_DECIMAL_PLACES = 2 + WEIGHT_DECIMAL_PLACES = 3 + DISCOUNT_LINE_OFFSET = 3 + + # Format strings + PRICE_FORMAT = "%.2f" + WEIGHT_FORMAT = "%.3f" + + # Display strings + UNIT_PRICE_INDENT = " " + MULTIPLICATION_SYMBOL = " * " + TOTAL_LABEL = "Total: " + WHITESPACE_CHAR = " " + NEWLINE = "\n" + DISCOUNT_OPEN_PAREN = "(" + DISCOUNT_CLOSE_PAREN = ")" + DISCOUNT_PREFIX = "-" - def initialize(columns = 40) + def initialize(columns = DEFAULT_COLUMNS) @columns = columns end def print_receipt(receipt) result = "" for item in receipt.items do - price = "%.2f" % item.total_price + price = PRICE_FORMAT % item.total_price quantity = self.class.present_quantity(item) name = item.product.name - unit_price = "%.2f" % item.price + unit_price = PRICE_FORMAT % item.price whitespace_size = @columns - name.size - price.size - line = name + self.class.whitespace(whitespace_size) + price + "\n" + line = name + self.class.whitespace(whitespace_size) + price + NEWLINE - if item.quantity != 1 - line += " " + unit_price + " * " + quantity + "\n" + if item.quantity != QUANTITY_THRESHOLD + line += UNIT_PRICE_INDENT + unit_price + MULTIPLICATION_SYMBOL + quantity + NEWLINE end result.concat(line); end for discount in receipt.discounts do product_presentation = discount.product.name - price_presentation = "%.2f" % discount.discount_amount + price_presentation = PRICE_FORMAT % discount.discount_amount description = discount.description result.concat(description) - result.concat("(") + result.concat(DISCOUNT_OPEN_PAREN) result.concat(product_presentation) - result.concat(")") - result.concat(self.class.whitespace(@columns - 3 - product_presentation.size - description.size - price_presentation.size)) - result.concat("-"); + result.concat(DISCOUNT_CLOSE_PAREN) + result.concat(self.class.whitespace(@columns - DISCOUNT_LINE_OFFSET - product_presentation.size - description.size - price_presentation.size)) + result.concat(DISCOUNT_PREFIX); result.concat(price_presentation); - result.concat("\n"); + result.concat(NEWLINE); end - result.concat("\n") - price_presentation = "%.2f" % receipt.total_price.to_f - total = "Total: " + result.concat(NEWLINE) + price_presentation = PRICE_FORMAT % receipt.total_price.to_f + total = TOTAL_LABEL whitespace = self.class.whitespace(@columns - total.size - price_presentation.size) result.concat(total, whitespace, price_presentation) return result.to_s end def self.present_quantity(item) - return ProductUnit::EACH == item.product.unit ? '%x' % item.quantity.to_i : '%.3f' % item.quantity + return ProductUnit::EACH == item.product.unit ? '%d' % item.quantity.to_i : WEIGHT_FORMAT % item.quantity end def self.whitespace(whitespace_size) whitespace = '' whitespace_size.times do - whitespace.concat(' ') + whitespace.concat(WHITESPACE_CHAR) end return whitespace end diff --git a/swift/SupermarketReceipt/ReceiptPrinter.swift b/swift/SupermarketReceipt/ReceiptPrinter.swift index eb6ea18..d04a249 100644 --- a/swift/SupermarketReceipt/ReceiptPrinter.swift +++ b/swift/SupermarketReceipt/ReceiptPrinter.swift @@ -1,6 +1,22 @@ public class ReceiptPrinter { - - private var columns: Int = 40 + // Constants for receipt formatting + private static let defaultColumns = 40 + private static let quantityThreshold = 1.0 + private static let priceDecimalPlaces = 2 + private static let weightDecimalPlaces = 3 + private static let discountLineOffset = 3 + + // Display strings + private static let unitPriceIndent = " " + private static let multiplicationSymbol = " * " + private static let totalLabel = "Total: " + private static let whitespaceChar = " " + private static let newline = "\n" + private static let discountOpenParen = "(" + private static let discountCloseParen = ")" + private static let discountPrefix = "-" + + private var columns: Int = defaultColumns public init(columns: Int) { self.columns = columns @@ -9,35 +25,35 @@ public class ReceiptPrinter { public func printReceipt(receipt: Receipt) -> String { var result = "" for item in receipt.items { - var price = String(format: "%.2f", item.totalPrice) + var price = String(format: "%.\(ReceiptPrinter.priceDecimalPlaces)f", item.totalPrice) var quantity = ReceiptPrinter.presentQuantity(item: item) var name = item.product.name - var unitPrice = String(format :"%.2f", item.price) + var unitPrice = String(format: "%.\(ReceiptPrinter.priceDecimalPlaces)f", item.price) var whitespaceSize = self.columns - name.count - price.count - var line = name + ReceiptPrinter.getWhitespace(whitespaceSize: whitespaceSize) + price + "\n" + var line = name + ReceiptPrinter.getWhitespace(whitespaceSize: whitespaceSize) + price + ReceiptPrinter.newline - if (item.quantity != 1) { - line += " " + unitPrice + " * " + quantity + "\n" + if (item.quantity != ReceiptPrinter.quantityThreshold) { + line += ReceiptPrinter.unitPriceIndent + unitPrice + ReceiptPrinter.multiplicationSymbol + quantity + ReceiptPrinter.newline } result.append(line) } for discount in receipt.discounts { var productPresentation = discount.product.name - var pricePresentation = String(format: "%.2f", discount.discountAmount) + var pricePresentation = String(format: "%.\(ReceiptPrinter.priceDecimalPlaces)f", discount.discountAmount) var description = discount.description result.append(description) - result.append("(") + result.append(ReceiptPrinter.discountOpenParen) result.append(productPresentation) - result.append(")") - result.append(ReceiptPrinter.getWhitespace(whitespaceSize: self.columns - 3 - productPresentation.count - description.count - pricePresentation.count)) - result.append("-") + result.append(ReceiptPrinter.discountCloseParen) + result.append(ReceiptPrinter.getWhitespace(whitespaceSize: self.columns - ReceiptPrinter.discountLineOffset - productPresentation.count - description.count - pricePresentation.count)) + result.append(ReceiptPrinter.discountPrefix) result.append(pricePresentation) - result.append("\n") + result.append(ReceiptPrinter.newline) } - result.append("\n") - var pricePresentation = String(format: "%.2f", Double(receipt.getTotalPrice())) - var total = "Total: " + result.append(ReceiptPrinter.newline) + var pricePresentation = String(format: "%.\(ReceiptPrinter.priceDecimalPlaces)f", Double(receipt.getTotalPrice())) + var total = ReceiptPrinter.totalLabel var whitespace = ReceiptPrinter.getWhitespace(whitespaceSize: self.columns - total.count - pricePresentation.count) result.append(total) result.append(whitespace) @@ -47,14 +63,14 @@ public class ReceiptPrinter { private static func presentQuantity(item: ReceiptItem ) -> String { return ProductUnit.Each == item.product.unit - ? String(format: "%x", Int(item.quantity)) - : String(format: "%.3f", item.quantity) + ? String(format: "%d", Int(item.quantity)) + : String(format: "%.\(weightDecimalPlaces)f", item.quantity) } private static func getWhitespace(whitespaceSize: Int) -> String { var whitespace = "" for i in 0..