Example #1
0
 // A helper function for avsserting products in the collection are correct.
 private void AssertProductAmountIsEqual(IProductAmountCollection collection, Mock <IProduct> product, double value)
 {
     Assert.That(collection.GetProductValue(product.Object), Is.EqualTo(value));
 }
Example #2
0
        /// <summary>
        /// Given a set of cash and a desired price, get the amount of the cash
        /// to meet the price (rounded up for safety)
        /// </summary>
        /// <param name="AvailableCash">The amount of cash avaliable.</param>
        /// <param name="price">The price to meet (roughly)</param>
        /// <returns>The appropriate cash for the price.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="AvailableCash"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">If <paramref name="price"/> is less than or equal to 0.</exception>
        public IProductAmountCollection ChangeForPrice(IProductAmountCollection AvailableCash, double price)
        {
            // ensure cash is not null.
            if (AvailableCash is null)
            {
                throw new ArgumentNullException(nameof(AvailableCash));
            }
            // ensure that the price is greater than 0
            if (price <= 0) // TODO, allow this to savely give change for 0. It's not that hard.
            {
                throw new ArgumentOutOfRangeException("Price must be greater than 0.");
            }

            // first, check that all available cash can meet the price.
            var totalCash = AvailableCash.Sum(x => x.Item2 * ProductPrices.GetProductValue(x.Item1));

            // If the total cash available is less than the price sought, return a copy of the available cash.
            if (totalCash < price)
            {
                return(AvailableCash.Copy());
            }

            // since we have more than we need,
            var result = new ProductAmountCollection();

            // start from the best and start making change, highest to lowest value.
            foreach (var coin in AvailableCash.OrderByDescending(x => ProductPrices.GetProductValue(x.Item1)))
            {
                // if none of that coin exist
                if (coin.Item2 == 0)
                {
                    // add it as zero
                    result.AddProducts(coin.Item1, 0);
                    // and skip to the next loop
                    continue;
                }

                // coin type
                var curr = coin.Item1;
                // coin value
                var val = ProductPrices.GetProductValue(curr);
                // if value is higher then remaining price.
                if (val > price)
                {
                    // add it as zero
                    result.AddProducts(curr, 0);
                    // and skip
                    continue;
                }

                // coin amount
                var amt = coin.Item2;

                // how many whole coins can fit into the price.
                var count = Math.Floor(price / val);

                // select cap coins at the available level.
                count = Math.Min(count, amt);

                // add to our change
                result.AddProducts(curr, count);

                // subtract the value we took out
                price -= val * count;
                // then go to the next coin.
            }

            // if there is a remainder
            if (price > 0)
            {
                // find the smallest coin available
                foreach (var coin in AvailableCash.OrderBy(x => ProductPrices.GetProductValue(x.Item1)))
                {
                    // if we have any available
                    if (result.GetProductValue(coin.Item1) < AvailableCash.GetProductValue(coin.Item1))
                    {
                        // add one
                        result.AddProducts(coin.Item1, 1);

                        // and move on.
                        break;

                        // by logic, only one should be needed as we guaranteed have
                        // more value in coins then the requested price and the
                        // remainder should be smaller than the smallest coin.
                    }
                }
            }

            // return the change.
            return(result);
        }