/// <summary> /// Process the uploaded file contents and return a list of records with completed calculations /// </summary> /// <param name="fileContents"></param> /// <returns></returns> public IEnumerable <CashRegisterRecord> ProcessFile(string fileContents) { // Parse the file contents to get a list of records IEnumerable <CashRegisterRecord> records = ParseFileContents(fileContents); // Calculate change and denominations for each record foreach (CashRegisterRecord record in records) { try { record.Change = CalculateChange(record.Owed, record.Paid); // Client's "twist" // If amount owed is divisible by 3, use random algorithm to determine bills and coins BillsAndCoinsAlgorithm algorithm = BillsAndCoinsAlgorithm.Greedy; if ((record.Owed * 100) % 3 == 0) { algorithm = BillsAndCoinsAlgorithm.Random; } record.ChangeText = GetBillsAndCoins(record.Change, algorithm); } catch (Exception ex) { // Label this record as having an error // Continue processing the rest of the records record.Error = true; } } return(records); }
/// <summary> /// Determine how many of each currency denomination is needed to make the provided amount /// </summary> /// <param name="amount"></param> protected virtual string GetBillsAndCoins(decimal amount, BillsAndCoinsAlgorithm algorithm = BillsAndCoinsAlgorithm.Greedy) { try { // Get all denominations (ordered by value descending) Denomination[] denominations = GetDenominations(); // String with bills and change written out StringBuilder sb = new StringBuilder(); if (amount < 0) { // Data isn't right... sb.Append("Customer still owes $" + (0 - amount)); } else if (amount == 0) { // There is nothing to calculate sb.Append("No change"); } else { // Starting amount, when a matching bill or coin is found, their value will be subtracted from this amount decimal amountLeft = amount; switch (algorithm) { case BillsAndCoinsAlgorithm.Greedy: // Use greedy algorithm to determine the minimum amount of bills and change // If I recall correctly, in this case, greedy algorithm will always produce the best result // Start with the highest denomination foreach (Denomination denomination in denominations) { decimal value = denomination.Value; string name = denomination.Name; // Calculate how many times this denomination fits in amount left int count = Convert.ToInt32(Math.Truncate(amountLeft / value)); if (count > 0) { // Append the amount to the result string sb.Append((sb.Length > 1 ? ", " : "") + count + " " + (count > 1 ? name.ToPlural() : name)); // Substract the amount from the running amount left amountLeft -= count * value; } } break; case BillsAndCoinsAlgorithm.Random: // Random algorithm // To make sure that bills and coins add up correctly and implementation is efficient and easy, // follow the same pattern as greedy algorithm, but randomly choose the number of bills / coins at each step. // At the end, get all the available pennies (or lowest denomination) // This will produce a correct result, but will add a degree of randomess Random rng = new Random(); // Start with the highest denomination for (int i = 0; i < denominations.Length; i++) { decimal value = denominations[i].Value; string name = denominations[i].Name; // Calculate how many times this denomination fits in amount left int count = Convert.ToInt32(Math.Truncate(amountLeft / value)); // Randomize if this is not the lowest denomination (penny) if (i < denominations.Length - 1) { count = rng.Next(count); } if (count > 0) { // Append the amount to the result string sb.Append((sb.Length > 1 ? ", " : "") + count + " " + (count > 1 ? name.ToPlural() : name)); // Substract the amount from the running amount left amountLeft -= count * value; } } break; default: throw new Exception("Unknown algorithm to determine bills and coins."); } // Note any amount that we were unable to process // This could only happen if user entered values under 1 cent (or similar situation) if (amountLeft > 0) { sb.Append((sb.Length > 1 ? " " : "") + "($" + amountLeft + " that cannot be given in change)"); } } return(sb.ToString()); } catch (Exception ex) { throw new Exception("Unable to determine bills and change for " + amount + ".", ex); } }