user1307346
user1307346

Reputation: 765

Calculating the note mix to dispense

I need to figure out a cash mix algorithm to dispense notes from the ATM. The goals of the algorithm are:

  1. To calculate the note mix to dispense the required amount to the customer.
  2. While doing so, to attempt the emptying of the cash cassettes as evenly as possible, so ideally (but not mandatory), the cash will run out in all cassettes at the same time.

The user wants to get different amounts of cash, such as 500$, 1200$, 6000$, etc. There is no average. The algorithm needs to figure out which notes and how many of them to dispense. Of course, the cassettes in the ATM can change to different values / counts.

Another limitation is that the ATM can present only 30 notes at the time, so the algorithm has to divide the notes in bunches, if calculated number of notes exceeds this limit, while considering the goal above (equal emptying).

Here is what i came up with:

//Represents a typical cash cassette.
class Cassette
{
   public int Denom { get; set; } //Denomination: (USD)50, (USD)100, etc.
   public int Count { get; set; } //Number of notes.
}

//Our cassettes.
List<Cassette> OriginalCashCassettes = new List<Cassette>();
List<Cassette> CloneCashCassettes = new List<Cassette>();

//Populated.
OriginalCashCassettes.Add(new Cassette { Denom = 50, Count = 1000 });
OriginalCashCassettes.Add(new Cassette { Denom = 100, Count = 1000 });
OriginalCashCassettes.Add(new Cassette { Denom = 200, Count = 1000 });

//Pass original cassettes to clone cassettes.
CloneCashCassettes = OriginalCashCassettes;

//Calculate mix for requested amount.
CalculateNoteMix(6000);

And the calculation itself:

private void CalculateNoteMix(int reqAmount)
{
    //1. Check if the amount is higher than combined counts.
    int totalCounts = 0;
    foreach (var item in CloneCashCassettes)
    {
        totalCounts += item.Denom * item.Count;
    }

    if (totalCounts < reqAmount)
    {
        Console.WriteLine("You're trying too high - maximum amount available is: " + totalCounts);
        return;
    }

    //2. Check if the amount is dispensable with current denoms.
    int lowestDenom = CloneCashCassettes.Min(c => c.Denom);
    if (reqAmount % lowestDenom != 0)
    {
        Console.WriteLine("Unable to dispense amount with current denoms");
        return;
    }

    //3. Calculate note mix to dispense.
    List<Cassette> noteMix = new List<Cassette>();

    do
    {
        //Sort cash cassettes by highest count first.
        CloneCashCassettes = CloneCashCassettes.OrderByDescending(c => c.Count).ToList();

        //Check if highest count denom can cover the amount.
        if (CloneCashCassettes[0].Denom <= reqAmount)
        {

            //Check if this denom already exists in the mix.
            Cassette noteMixCassette = noteMix.Find(n => n.Denom == CloneCashCassettes[0].Denom);
            if (noteMixCassette == null)
            {
                //Add denom to the note mix.
                noteMix.Add(new Cassette { Denom = CloneCashCassettes[0].Denom, Count = 1 });
            }
            else
            {
                //Increase denom count in the note mix.
                noteMixCassette.Count += 1;
            }

            //Reduce denom count in the cash cassette.
            CloneCashCassettes[0].Count -= 1;
            //Reduce the amount by denom.
            reqAmount -= CloneCashCassettes[0].Denom;
        }
        else
        {
            //The amount is smaller than denom => the denom is unusable - remove it.
            CloneCashCassettes.RemoveAt(0);
        }

    //Keep looping until the amount is 0.
    } while (reqAmount > 0);

    //Print resulting note mix.
    Console.WriteLine("For the amount of " + reqAmount + ", our note mix is:");
    foreach (var item in noteMix)
    {
        Console.WriteLine("Denom: " + item.Denom + " x " + "Count: " + item.Count + " = " + item.Denom * item.Count);
    }
}

Using this code, if the user asks for $400, then the note mix is:

Denom: 50 x Count: 2 = 100
Denom: 100 x Count: 1 = 100
Denom: 200 x Count: 1 = 200

Or if the user asks for $25,000, then:

Denom: 50 x Count: 72 = 3600
Denom: 100 x Count: 72 = 7200
Denom: 200 x Count: 71 = 14200

Problems:

  1. This code appears to work fine with denoms of 50 and higher. However, it has a problem with the denom of 20. Any idea how to resolve it?

    Example:

        OriginalCashCassettes.Add(new Cassette { Denom = 20, Count = 1000 });
        OriginalCashCassettes.Add(new Cassette { Denom = 50, Count = 1000 });
        OriginalCashCassettes.Add(new Cassette { Denom = 100, Count = 1000 });
    

    The user asks for $200, which is dispensable. I start to subtract: 200-100-50-20 = 30 - 20 = 10 -> unable to dispense.

  2. Same problem with denom of 20 exists in the check if the amount is dispensable (#2 in code).

    Example: The cassettes configured as above with denom of 20. User asks for $210, which should be dispensable (100+50+20+20+20).

  3. Any ideas how to improve this algorithm in general, so it will be more efficient / faster?

Thanks.

Upvotes: 2

Views: 4445

Answers (1)

Jamiec
Jamiec

Reputation: 136144

The problem you've encountered, basically, is that you're algorithm leads you to a place where you cannot dispense any more... a dead end (ie, left with $10 to dispense but you do not have that denomination).

What I would do to combat this is to generate all possible permutations of valid dispensations, and then pick which one is "best" or most optimal in terms of rules such as "even dispensation of bills". There may be some shortcuts you can take later on, such as ruling out obviously bad choices, but you'll understand the "optimization" bit much easier if the thing actually works!

I started off with this example (http://www.dotnetperls.com/change) which is a rather rudimentary algorithm for determining the permutations of change available for a given set of coins and a required amount. This is the same basic problem as yours.

public static void Main(string[] args)
{
    List<int> notes = new List<int>();
    List<int> amounts = new List<int>() { 50,100,200 };
    Change(notes, amounts, 0, 0, 250);
}

static void Change(List<int> notes, List<int> amounts, int highest, int sum, int goal)
{
    //
    // See if we are done.
    //
    if (sum == goal)
    {
        Display(notes, amounts);
        return;
    }
    //
    // See if we have too much.
    //
    if (sum > goal)
    {
        return;
    }
    //
    // Loop through amounts.
    //
    foreach (int value in amounts)
    {
        //
        // Only add higher or equal amounts.
        //
        if (value >= highest)
        {
        List<int> copy = new List<int>(notes);
        copy.Add(value);
        Change(copy, amounts, value, sum + value, goal);
        }
    }
}

Here is a live example: http://rextester.com/HIJEQC83701

Asking this code for the permutations for $250 using combinations of 50's,100's and 200's gives the following output:

50: 5
100: 0
200: 0

50: 3
100: 1
200: 0

50: 1
100: 2
200: 0

50: 1
100: 0
200: 1

From there, its easy enough to pick the option that either

a) Uses the most even spread of notes
b) Uses a spread of notes which leaves the overall casettes in the most balanced position.

I'll leave that bit to you!

Upvotes: 5

Related Questions