/// <summary> /// Generates the output of change due or various errors /// If no violations have been tripped - figure out currency denominations for return /// If change due is divisible by 3 Mod3ChangeGeneration will be called. /// Otherwise change is given as optimally as possible. /// </summary> /// <param name="regionCurrency">The region currency.</param> /// <param name="r">random seed</param> /// <param name="randomizeAllChange">if set to <c>true</c> [randomize all change]. otherwise randomizes decimal value only</param> internal void GenerateChange(IRegionCurrency regionCurrency, Random r, bool randomizeAllChange) { if (violation == null) { Change.Clear(); decimal changeValue = Math.Round(tendered - cost, 2, MidpointRounding.AwayFromZero); string[] stringSplit = changeValue.ToString().Split('.'); int x = stringSplit.Length > 1 ? int.Parse(stringSplit[1]) : 0; if (x % 3 == 0) { //wasn't sure if all money due back should be subject to random behavior //or just the decimal value - I implemented both; if (randomizeAllChange) { Change = Mod3ChangeGeneration(changeValue, regionCurrency, r); } else //randomizes decimal value only. { Change = OptimizedChangeReturned(decimal.Parse(stringSplit[0]), regionCurrency); Change.AddRange(Mod3ChangeGeneration(decimal.Parse(string.Format("0.{0}", x)), regionCurrency, r)); } } else { Change = OptimizedChangeReturned(changeValue, regionCurrency); } } else { Change.Add(string.Format("Error with input ({0}):", RawInput)); Change.Add(violation.Value); } }
/// <summary> /// Optimizes the change returned. /// Gives the most efficient change back possible. /// </summary> /// <param name="changeValue">The change value.</param> /// <param name="regionCurrency">The region currency.</param> /// <returns></returns> private List <string> OptimizedChangeReturned(decimal changeValue, IRegionCurrency regionCurrency) { List <string> toReturn = new List <string>(); foreach (var c in regionCurrency.GetDescendingOrderedCurrencies()) { if (c.Value <= changeValue) { int times = (int)(changeValue / c.Value); toReturn.Add(string.Format("{0} {1}", times, times > 1 ? c.PluralName : c.Name)); changeValue -= (times * c.Value); } } return(toReturn); }
/// <summary> /// If the change due is divisible by 3, give random denominations /// these random denominations must equal the appropriate total amount /// of change due /// </summary> /// <param name="changeValue">The change value.</param> /// <param name="regionCurrency">The region currency.</param> /// <param name="r">The random seed</param> /// <returns></returns> private List <string> Mod3ChangeGeneration(decimal changeValue, IRegionCurrency regionCurrency, Random r) { Dictionary <Currency, int> changeDue = new Dictionary <Currency, int>(); List <string> toReturn = new List <string>(); foreach (Currency c in regionCurrency.Denominations) { changeDue.Add(c, 0); } while (changeValue > 0) { Currency tmp = regionCurrency.Denominations[r.Next(regionCurrency.Denominations.Count)]; if (changeValue >= tmp.Value) { try { int maxNumberOfTimes = (int)(changeValue / tmp.Value) + 1; //adding 1 because random max is exclusive int actualTimes = r.Next(maxNumberOfTimes); changeDue[tmp] += actualTimes; changeValue -= (actualTimes * tmp.Value); } catch (OverflowException) { //eat the overflow exception and try again //this happens when the initial change amount due is at or near the limit for Int32 //and we try to generate change using something less than 1 ex: a nickel. //allow the system to try larger denominations to reduce the total change due being tracked } } } //populate the change denominations to return foreach (Currency c in regionCurrency.GetDescendingOrderedCurrencies()) { if (changeDue[c] > 0) { toReturn.Add(string.Format("{0} {1}", changeDue[c], changeDue[c] > 1 ? c.PluralName : c.Name)); } } return(toReturn); }
/// <summary> /// Processes the transactions. /// </summary> /// <param name="inputFile">The input file.</param> /// <param name="outputFile">The output file.</param> /// <param name="dataDumpThreshold">The data dump threshold.</param> /// <param name="randomizeAllChange">if set to <c>true</c> [randomize all change].</param> /// <param name="regionCurrency">The region currency.</param> /// <returns></returns> public static bool ProcessTransactions(string inputFile, string outputFile, int dataDumpThreshold, bool randomizeAllChange, IRegionCurrency regionCurrency) { if (regionCurrency == null) { return(false); } //book-keeping int start = 0; int dataDumpOffset = 0; //number of lines to read at once int take = 50000; Mutex _m = new Mutex(); Dictionary <int, Transaction> transactions = new Dictionary <int, Transaction>(); while (true) { string[] v = null; try { v = File.ReadLines(inputFile).Skip(start).Take(take).ToArray(); } catch (Exception e) { Console.WriteLine(e.Message); return(false); } //using Parallel.ForEach for increased performance when calculating change Parallel.ForEach(v, (s, x, i) => { Transaction tmp = new Transaction(s); tmp.GenerateChange(regionCurrency, new Random(Guid.NewGuid().GetHashCode()), randomizeAllChange); int index = (int)i + start; _m.WaitOne(); transactions.Add(index, tmp); _m.ReleaseMutex(); }); //section to write transaction data to disk - preventing out of memory issues if (transactions.Count % dataDumpThreshold == 0) { try { using (StreamWriter writer = new StreamWriter(outputFile, append: true)) { int offset = dataDumpThreshold * dataDumpOffset; for (int i = 0; i < transactions.Count; i++) { writer.WriteLine(transactions[i + offset].ToString()); } } } catch (Exception e) { Console.WriteLine(e.Message); return(false); } transactions.Clear(); dataDumpOffset++; } start += take; if (v.Length < take) { break; } } if (transactions.Count > 0) //write the remaining transactions { try { using (StreamWriter writer = new StreamWriter(outputFile, append: true)) { int offset = dataDumpThreshold * dataDumpOffset; for (int i = 0; i < transactions.Count; i++) { writer.WriteLine(transactions[i + offset].ToString()); } } } catch (Exception e) { Console.WriteLine(e.Message); return(false); } } return(true); }