Calculating the note mix to dispense - c#

I need to figure out a cash mix algorithm to dispense notes from the ATM. The goals of the algorithm are:
To calculate the note mix to dispense the required amount to the customer.
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:
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.
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).
Any ideas how to improve this algorithm in general, so it will be more efficient / faster?
Thanks.

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!

Related

Algorithm to match buy and sell orders

I am building an online stock game. All orders are at exactly market price. There is no real "bidding", only straight buy and sell. So this should be easier. Is there an algorithm that tackles the following problem:
Different orders with different volume. For example, the following buy orders are made...
order A for 50 shares
order B for 25 shares
order C for 10 shares
order D for 5 shares
order E for 5 shares
order F for 30 shares
There is a sell order G for 100 shares.
I need to find the right combination of the above buy orders in a way that gets as close to 100 shares as possible, without going over....
The Knapsack algorithm would work, but the performance will degrade very fast with a large number of users and orders being made. Is there a more efficient way to do this?
EDIT:
Here is my modified knapsack algorithm:
static int KnapSack(int capacity, int[] weight, int itemsCount)
{
int[,] K = new int[itemsCount + 1, capacity + 1];
for (int i = 0; i <= itemsCount; ++i)
{
for (int w = 0; w <= capacity; ++w)
{
if (i == 0 || w == 0)
K[i, w] = 0;
else if (weight[i - 1] <= w)
K[i, w] = Math.Max(weight[i - 1] + K[i - 1, w - weight[i - 1]], K[i - 1, w]);
else
K[i, w] = K[i - 1, w];
}
}
return K[itemsCount, capacity];
}
The only problem is that it is really bad on performance when the numbers are high.
/*
Given array prices, return max profit w/ 1 buy & 1 sell
Ex. prices = [7,1,5,3,6,4] -> 5 (buy at $1, sell at $6)
For each, get diff b/w that & min value before, store max
Time: O(n)
Space: O(1)
*/
class Solution {
public:
int maxProfit(vector<int>& prices) {
int minValue = prices[0];
int maxDiff = 0;
for (int i = 1; i < prices.size(); i++) {
minValue = min(minValue, prices[i]);
maxDiff = max(maxDiff, prices[i] - minValue);
}
return maxDiff;
}
};
ref: https://github.com/neetcode-gh/leetcode/blob/main/cpp/neetcode_150/03_sliding_window/best_time_to_buy_and_sell_stock.cpp
I am still not very clear with the example you gave in the question description, for any knapsack problem we need 2 things capacity and profit. Here you just provided capacity.
Considering you just need to reach 100 as close as possible without worrying about the profit then it's much simple and can have multiple ways to do it.
One way is to just take all the bigger one which is smaller than the remaining capacity. If they are bigger than the remaining capacity then go to the next smaller one.
Time: O(NlogN) for sorting
Space: O(1)
function getMax(arr, maxCap) {
arr.sort((a, b) => b - a);
let index = 0;
let cap = 0;
while (cap !== maxCap && index < arr.length) {
const remainingCap = maxCap - cap;
if (remainingCap >= arr[index]) {
cap += arr[index];
}
index++;
}
}

C# WinsForm, Frequency Distribution Table [Updated]

Update 01
Thanks to Caius, found the main problem, the logic on the "if" was wrong, now fixed and giving the correct results. The loop still create more positions than needed on the secondary List, an extra position for each number on the main List.
I've updated the code bellow for refence for the following question:
-001 I can figure out why it create positions that needed, the for loop should run only after the foreach finishes its loops correct?
-002 To kind of solving this issue, I've used a List.Remove() to remove all the 0's, so far no crashes, but, the fact that I'm creating the extra indexes, and than removing them, does means a big performance down if I have large list of numbers? Or is an acceptable solution?
Description
It supposed to read all numbers in a central List1 (numberList), and count how many numbers are inside a certain (0|-15 / 15|-20) range, for that I use another List, that each range is a position on the List2 (numberSubList), where each number on List2, tells how many numbers exists inside that range.
-The range changes as the numbers grows or decrease
Code:
void Frequency()
{
int minNumb = numberList.Min();
int maxNumb = numberList.Max();
int size = numberList.Count();
numberSubList.Clear();
dGrdVFrequency.Rows.Clear();
dGrdVFrequency.Refresh();
double k = (1 + 3.3 * Math.Log10(size));
double h = (maxNumb - minNumb) / k;
lblH.Text = $"H: {Math.Round(h, 2)} / Rounded = {Math.Round(h / 5) * 5}";
lblK.Text = $"K: {Math.Round(k, 4)}";
if (h <= 5) { h = 5; }
else { h = Math.Round(h / 5) * 5; }
int counter = 1;
for (int i = 0; i < size; i++)
{
numberSubList.Add(0); // 001 HERE, creating more positions than needed, each per number.
foreach (int number in numberList)
{
if (number >= (h * i) + minNumb && number < (h * (i + 1)) + minNumb)
{
numberSubList[i] = counter++;
}
}
numberSubList.Remove(0); // 002-This to remove all the extra 0's that are created.
counter = 1;
}
txtBoxSubNum.Clear();
foreach (int number in numberSubList)
{
txtBoxSubNum.AppendText($"{number.ToString()} , ");
}
lblSubTotalIndex.Text = $"Total in List: {numberSubList.Count()}";
lblSubSumIndex.Text = $"Sum of List: {numberSubList.Sum()}";
int inc = 0;
int sum = 0;
foreach (int number in numberSubList)
{
sum = sum + number;
int n = dGrdVFrequency.Rows.Add();
dGrdVFrequency.Rows[n].Cells[0].Value = $"{(h * inc) + minNumb} |- {(h * (1 + inc)) + minNumb}";
dGrdVFrequency.Rows[n].Cells[1].Value = $"{number}";
dGrdVFrequency.Rows[n].Cells[2].Value = $"{sum}";
dGrdVFrequency.Rows[n].Cells[3].Value = $"{(number * 100) / size} %";
dGrdVFrequency.Rows[n].Cells[4].Value = $"{(sum * 100) / size} %";
inc++;
}
}
Screen shot showing the updated version.
I think, if your aim is to only store eg 17 in the "15 to 25" slot, this is wonky:
if (number <= (h * i) + minNumb) // Check if number is smaller than the range limit
Because it's found inside a loop that will move on to the next range, "25 to 35" and it only asks if the number 17 is less than the upper limit (and 17 is less than 35) so 17 is accorded to the 25-35 range too
FWIW the range a number should be in can be derived from the number, with (number - min) / number_of_ranges - at the moment you create your eg 10 ranges and then you visit each number 10 times looking to put it in a range, so you do 9 times more operations than you really need to

Convert money input into coins

ive just started some classes on c# and have been given an assignment with the following rules:
Prompt the user to enter an amount of dollars and cents. For example 1.18
- Display the number of quarters, dimes, nickels, and pennies to make that amount
Example If they entered 2.16 it should say:
8 quarters, 1 dimes, 1 nickels, 1 pennies
the problem that i run into is that this only seems to work if they type the money value as a whole. so if they wanted to type $1.18 they would type 118 and it would work just fine, but as soon as they type 1.18 it crashes. another example would be if they were to type 765 for $7.65 it would work fine, however if they type it correctly as 7.65 it would fail. sorry for the lame question, im super new, thanks for the help!
int totalCash;
Console.WriteLine("input money");
string moneyString = Console.ReadLine();
totalCash = int.Parse(moneyString);
int quarter = totalCash / 25;
totalCash %= 25;
int dime = totalCash / 10;
totalCash %= 10;
int nickel = totalCash / 5;
totalCash %= 5;
int penny = totalCash / 1;
totalCash %= 1;
Console.WriteLine("{0} quarters, {1} dimes, {2} nickels, {3} pennies", quarter, dime, nickel, penny);
```
There are lots of ways get the result but this the best approach I ever tried :
public static string ConvertMoneyIntoCoins(double money)
{
int cents = (int)(Math.Round(money, 2) * 100);
var coins = new[] {
new { Name = "Quarters", Value = 25 }, new { Name = "Dimes", Value = 10 },
new { Name = "Nickels", Value = 5 }, new { Name = "Pennies", Value = 1 }
};
var changes = coins.Select(coin => new { Amt = Math.DivRem(cents, coin.Value, out cents), Coin = coin }).Where(x => x.Amt != 0).ToList();
var strBld = new StringBuilder();
foreach (var change in changes)
{
strBld.Append(change.Amt + " " + change.Coin.Name + ", ");
}
return strBld.ToString();
}
It's working when you enter whole number should be the clue you pay attention to. If you assume the whole number is dollars, you can not mod by a whole number. All of your divisors are a factor of 100 too big. When you do that, you'll notice that you have the wrong data type as well. Note that i disagree with using tryparse when debugging as it eats errors. You should be running it in debug mode and then you would get an actual stack trace and a line it crashes at.

Unexpected results when counting how often sequences occur in a random distribution

Given a coin game: you start with a dollar and a coin is flipped. If it's heads the dollar is doubled, if it's tails the game ends. However if head's is flipped again the payoff is now quadrupled and if head is flipped 3 times 8x and so on. The paradox is that the expect value is 1/2*1+1/4*2+1/8*4... = infinity.
So you if you play the game long enough you should be getting progressively richer. Monte carlo simulation suggests not.
This is simulation of the famous St. Petersburg Paradox
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sorrow
{
class Program
{
static void Main(string[] args)
{
Random rnd = new Random(Environment.TickCount);
double totalSum = 0;
int bigWins = 0;
double iterations = 1000;
for (int z = 0; z < 10; z++)
{
iterations *= 10;
for (double i = 1; i < iterations; i++)
{
int sum = 1;
int a = 1;
while (a == 1)
{
//generate a random number between 1 and 2
a = rnd.Next(1, 3);
if (a == 1)
{
sum *= 2;
}
if (sum > 8000&&sum<12000)// given discrete probability landing 13 times
{
// if the sum is over 8000 that means that it scored 1 13 times in a row (2^13) - that should happen
//once every 8192 times. Given that we run the simulation 100 000 000 times it should hover around
// 100 000 000/8192
//However is much , much bigger
bigWins++;
}
}
totalSum += sum;
}
Console.WriteLine("Average gain over : "+iterations+" iterations is:" + totalSum / iterations);
Console.WriteLine("Expected big wins: " + iterations / 8192 + " Actual big wins: " + bigWins);
Console.WriteLine();
}
}
}
}
As you can see we should expect 7 times smaller number. This makes me think that perhaps c# random is prone to choosing the same number over and over again?
Is it true or there is something wrong with my code?
How might I fix the issue?
You have two bugs. Your loop starts after a win, so the chance of a big win is 1/2^12, and you keep incrementing bigwins for additional wins after 12.
Try
static void Main(string[] args)
{
Random rnd = new Random(Environment.TickCount);
double iterations = 1000;
for (int z = 0; z < 10; z++)
{
double totalSum = 0;
int bigWins = 0;
iterations *= 10;
for (double i = 1; i < iterations; i++)
{
int sum = 2;
int a = 1;
while (a == 1)
{
//generate a random number between 1 and 2
a = rnd.Next(1, 3);
if (a == 1)
{
sum *= 2;
}
if (sum > 8000)
{
// if the sum is over 8000 that means that it scored 1 12 times in a row (2^12) - that should happen
//once every 4096 times. Given that we run the simulation 100 000 000 times it should hover around
// 100 000 000/4096
bigWins++;
break;
}
}
totalSum += sum;
}
Console.WriteLine("Average gain over : " + iterations + " iterations is:" + totalSum / iterations);
Console.WriteLine("Expected big wins: " + iterations / 4096 + " Actual big wins: " + bigWins);
Console.WriteLine();
}
Console.ReadKey();
}
outputs something like:
Average gain over : 10000 iterations is:12.6774
Expected big wins: 2.44140625 Actual big wins: 1
Average gain over : 100000 iterations is:14.09468
Expected big wins: 24.4140625 Actual big wins: 21
Average gain over : 1000000 iterations is:14.022718
Expected big wins: 244.140625 Actual big wins: 249
Average gain over : 10000000 iterations is:14.0285748
Expected big wins: 2441.40625 Actual big wins: 2456
Average gain over : 100000000 iterations is:14.00012582
Expected big wins: 24414.0625 Actual big wins: 24574
Average gain over : 1000000000 iterations is:14.000105548
Expected big wins: 244140.625 Actual big wins: 244441
Average gain over : 10000000000 iterations is:13.9990068676
Expected big wins: 2441406.25 Actual big wins: 2440546
What you are looking for is the probability that the game gets to or continues past $8000 which is 1 minus the sum of the probabilities of ending before $8000
Probability of ending after...
0 rounds 1/2 $2
1 round 1/4 $4
2 rounds 1/8 $8
3 rounds 1/16 $16 (same as 1/(2^(rounds+1))
...
12 rounds 1/2^13 $8192 (in your code you are off by one round, you get to $8192 after 12 wins, not 13
sum all of the probabilities of ending before $8192 and you get 0.999755859
So... your probability of a game getting to at least $8192 is 1-0.999756 or 0.000244141
Compare this to the probability of 1/8192 = 0.0001220703125 and you see you are off by about a factor of 2.
This doesn't change the fact that Random isn't a good approximation of random, and your expected results will still be off.
If you want to use RNGCryptoServiceProvider you can do the following
initialize a RNGCryptoServiceProvider somewhere in your class
RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
Then where you are assigning the value a you can do the following
//generate a random number between 1 and 2
//allocate an array of bytes to be populated by rngCsp
byte[] randomNumber = new byte[1];
//populate the array with a random byte
rngCsp.GetBytes(randomNumber);
//if the random byte is [0,125] return 1 and if [126,255] return 2
a = randomNumber[0] < 126 ? 1 : 2;
If you are interested in calculating the count of how many times a sequence of 13 or more ones occur, the below code may be of interest to you. It may not be as fast as the original code, but I think it may be slightly easier to read and understand (which I think is important, but because part of the reason why it took so long to spot the bugs in the original code was that it was a little hard to follow the logic). Basically, it keeps a queue of the last 13 items, and checks whether they are all 1.
Note that the calculation I have used to determine the expected number of sequences is also different to yours. I don't just divide by 8192, instead I do (iterations - (iterations * (1 - (1m/8192m)))). I don't think that calculation is 100% right, but it is more accurate than the original.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp4
{
internal class Program
{
private static void Main(string[] args)
{
var queue = new Queue<int>();
var rnd = new Random(Environment.TickCount);
int bigWins = 0;
long iterations = 10000000;
const int sequenceLength = 13;
double probability = 1 / Math.Pow(2, sequenceLength);
for (int z = 0; z < iterations; z++)
{
var a = rnd.Next(1, 3);
queue.Enqueue(a);
if (queue.Count == sequenceLength)
{
if (queue.Distinct().Count() == 1 && queue.First() == 1)
{
bigWins++;
}
queue.Dequeue();
}
}
Console.WriteLine("Expected big wins: " + (iterations - (iterations * (1 - probability))) + " Actual big wins: " + bigWins);
Console.ReadLine();
}
}
}

C# - Loop ending result

I'm going to try to explain this with the best of my ability, so please bear with me. I'm making a game, and basically there's 100 yards. you start at 100, and its on a loop, generating numbers and subtracting them from the 100 yards. Once the number hits 0, the loop will stop.
Take a look at this code:
int yardsLeft = 100;
// this is on a loop until 'yardsLeft = 0'
if (yardsLeft >= 80)
{
// 10% chance of generating a number 80-100
// 20% chance of generating a number 40-80
// 70% chance of generating a number 1-40
// Say it hits the 10% chance and generates the number 85 - there's 15 yards left
// it will pass through the if statements entering the `if (yardsLeft < 40)` - say that hits 20 once more. at the end once the yardsLeft finally equals 0, the yards added up each loop will be over 100. in this case 120
-------------------------------
// but if the generated number generates a 70% chance and hits a number 1-20, it's going to stay in the `yardsLeft > 80` if statment-
// therefore having the potential to exceed the number '100' once the `yardsLeft = 0`
}
else if (yardsLeft >= 40 && yardsLeft <= 79) { } // this would activate if the 20% chance got generated
if (yardsLeft < 40)
{
// 10% chance of generating a number 30-39
// 20% chance of generating a number 10-29
// 70% chance of generating a number 1-9
}
My problem:
if the generated number generates a 70% chance and hits a number 1-20, it's going to stay in the yardsLeft > 80 if statment,
therefore having the potential to exceed the number '100' once the yardsLeft = 0
So how if it did enter the yardsLeft >= 80, how can I make sure it generates a number that which at the end, it generated exactly 100 yards (numbers added up)
Here is my loop:
while (yardsLeft > 0)
{
int[] playResult = new int[i + 1];
playResult[i] = r.Next(1, 4);
switch (playResult[i])
{
case 1:
Console.WriteLine(BuffaloBills.QB + " hands it off to " + BuffaloBills.RB + " for a gain of " + Calculations.Play() + " yards. \n");
yardsLeft -= gained;
i++;
break;
case 2:
Console.WriteLine(BuffaloBills.QB + " passes it " + BuffaloBills.WR + " for a gain of " + Calculations.Play() + " yards. \n");
yardsLeft -= gained;
i++;
break;
case 3:
Console.WriteLine(BuffaloBills.QB + " doesn't find anyone open so he rushes for a gain of " + Calculations.Play() + " yards. \n");
yardsLeft -= gained;
i++;
break;
}
}
Here's my variables
public static Random r = new Random();
public static int gained;
public static int yardsLeft = 100;
public static int i = 0;
public static int chance = r.Next(1, 101);
Instead of just returning the number from your top if/else if/else statements, wait until the end of the block and return there. That will allow you to gather all of the logic to make the calculation in one spot.
private int Play(int yardsLeft) // Pass in the total yards left here
{
var yards = 0;
if (yardsLeft >= 80)
{
yards = // The result of your 10/20/70% calculation
}
else if (yardsLeft >= 40) { } // Same deal
else { } // Again, same as in the if
return yards > yardsLeft ? yardsLeft : yards;
}
You could also rework this to provide a string that would say whether or not a Touchdown was scored. (Have the yardsLeft parameter be a ref so that you can adjust the remaining yards inside this method.)

Categories