I want to calculate the amount to charge my customers, when they buy licenses of my product.
I sell it in ranges of licenses:
1-10 : $50/user
11-20 : $40/user
21-30 : $30/user
31-50 : $20/user
So when someone purchases 136 licenses, I will charge him:
50 x 2 x $20 = $2000
30 x 1 x $30 = $900
6 x $50 = $300
I'm looking for an algorithm on how to process the given number and break it into number of occurrences in a range..
How can I do this in plain C# or LINQ?
------------ EDIT ----------------------------
I started a less confusing question (Algorithm for Fogbugz pricing scheme) and I got the answer I've been looking for.
Thank you all..
If presented with this price structure I would think that it is in the customer's best interest to minimize the cost by buying the package that best suits their need. The following algorithm uses dynamic programming to calculate the minimal possible price to exactly buy a certain number of licenses (you can save money by buying more than you need, although I haven't implemented that):
int getPrice(int n)
{
if (n >= 1 && n <= 10) return 50 * n;
if (n >= 11 && n <= 20) return 40 * n;
if (n >= 21 && n <= 30) return 30 * n;
if (n >= 31 && n <= 50) return 20 * n;
throw new Exception("Impossible");
}
int minimizePrice(int n)
{
int[] minimumPrice = new int[n + 1];
for (int i = 1; i <= n; ++i)
{
minimumPrice[i] = int.MaxValue;
for (int j = Math.Max(0, i - 50); j < i; ++j)
{
minimumPrice[i] = Math.Min(minimumPrice[i],
minimumPrice[j] + getPrice(i - j));
}
}
return minimumPrice[n];
}
For 70 licenses the minimal price is $1400 which can be obtained by buying 2 blocks of 35 licenses. You are suggesting a greedy algorithm. This will confuse your customers. A clever customer will place two orders instead of one large order and save $400.
I'd suggest changing your prices so that there is no upper limit to the number of licenses you can buy at $20 each.
This looks like it would be very similar to algorithms that make change for purchases (which coins to choose). The only difference is that you're comparing against a range instead of a single number.
The code could look something like this:
var val = 136;
var price = 0;
while (val > 0)
{
var range = FindMatchingRange(val); // Use a dictionary, list, or array.
var number = Math.Min(val, range.Max);
price += range.CostPerUser * number;
val -= number;
}
If I were a person who needed 10 licenses, under your suggested pricing plan why would I ever buy just 10 licenses?
10 licenses * $50/license = $500
11 licenses * $40/license = $440
What you would want is plan that lowers the cost for the most recently purchased licenses. So that for person wanting 11 licenses they would pay:
(10 licenses * $50/license) + (1 license * $40/license) = $540
A possible plan would look like this:
first 10 licenses (1-10): $50/user
next 10 licenses (11-20): $40/user
next 10 licenses (21-30): $30/user
all licenses after that (31+) : $20/user
Writing code to compute final cost for any number of users is a simple exercise. The calculation for someone purchasing 136 licenses would look like this:
(10 licenses * $50/license) + (10 licenses * $40/license) + (10 licenses * $30/license) + (106 licenses * $20/license) = $500 + $400 + $300 + $2120 = $3,220.
The original pricing plan is wacky, in my opinion. Take the customer who purchased 130 licenses last year who comes back and wants 10 more. What justification is there for charging them the highest rate? They are a high volume customer, you want to sell them (and they justifiably expect to receive) additional licenses at the lowest "marginal" price.
I made a calculation class for you... just more customer-orientated.
It calculates the cheapest price possible with your defined priceranges.
Example: 136 Licenses
50 Licenses 20$ each ( because: 31-50 : $20/user )
50 Licenses 20$ each ( because: 31-50 : $20/user )
36 Licenses 20$ each ( because: 31-50 : $20/user )
TOTAL: 1720
Example 130 Licenses
50 Licenses 20$ each
50 Licenses 20$ each
30 Licenses 30$ each
TOTAL: 1900
Code for the class:
public class PriceCalculator
{
public List<OrderPackage> CalculateCheapestPrice(Int32 AmountOfLicenses,
List<PriceRange> PriceRanges, out Double Total)
{
List<OrderPackage> result = new List<OrderPackage>();
Total = 0;
Int32 AmountsOfLicensesleft = AmountOfLicenses;
PriceRanges.Sort(ComparePrice);
for (int i = 0; i < PriceRanges.Count; i++)
{
for (int j = PriceRanges[i].MaxAmount; j >= PriceRanges[i].MinAmount; j--)
{
if (j <= AmountsOfLicensesleft)
{
OrderPackage Order = new OrderPackage();
Int32 AmountOfThisPackage = AmountsOfLicensesleft / j;
//Int32 AmountForThisPrice = Convert.ToInt32(Math.Floor(tmp));
Order.PriceRange = PriceRanges[i];
Order.AmountOfLicenses = j;
Total += Order.AmountOfLicenses * Order.PriceRange.PricePerLicense;
for (int k = 0; k < AmountOfThisPackage; k++)
{
result.Add(Order);
}
AmountsOfLicensesleft = AmountsOfLicensesleft - (AmountOfThisPackage * j);
}
}
}
return result;
}
private static int ComparePrice(PriceRange x, PriceRange y)
{
if (x.PricePerLicense == y.PricePerLicense)
return 0;
if (x.PricePerLicense > y.PricePerLicense)
return 1;
if (x.PricePerLicense < y.PricePerLicense)
return -1;
return 0;
}
public class OrderPackage
{
public PriceRange PriceRange { get; set; }
public Int32 AmountOfLicenses { get; set; }
}
public class PriceRange
{
public int MinAmount { get; set; }
public int MaxAmount { get; set; }
public Double PricePerLicense { get; set; }
}
}
Usage example:
private void button1_Click(object sender, EventArgs e)
{
// Preparing PriceRangeDefinitions
List<PriceCalculator.PriceRange> PriceRangeDefinitions = new List<PriceCalculator.PriceRange>();
PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 1, MaxAmount = 10, PricePerLicense = 50 });
PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 11, MaxAmount = 20, PricePerLicense = 40 });
PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 21, MaxAmount = 30, PricePerLicense = 30 });
PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 31, MaxAmount = 50, PricePerLicense = 20 });
// Start the Calculation
PriceCalculator calculator = new PriceCalculator();
Double Total;
List<PriceCalculator.OrderPackage> Packages =
calculator.CalculateCheapestPrice(130, PriceRangeDefinitions, out Total);
// Show Proof of Concept
String ProofOfConcept = String.Empty;
for (int i = 0; i < Packages.Count; i++)
{
ProofOfConcept += Packages[i].AmountOfLicenses.ToString() + " Licenses " +
Packages[i].PriceRange.PricePerLicense.ToString() + "$ each" + Environment.NewLine;
}
ProofOfConcept += Environment.NewLine + "TOTAL: " + Total.ToString();
MessageBox.Show(ProofOfConcept);
}
A KeyValuePair collection or dictionary maybe?
Related
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++;
}
}
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
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.
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!
All,
I'm having an issue wrapping my head around how to find the ideal relationship between 2 values while also having to fit those items into a certain position. I honestly don't even know where to begin. I researched the knapsack problem, but there is no positional requirements.
Example:
I have $50.00 to spend on food. I need to eat 4 meals (breakfast, lunch, dinner, and a snack - which can be breakfast or lunch). Each meal has 4? properties; name, slot, calories, and cost. My goal is to eat the most calories while staying under my $50.00 allotment.
class Meal
{
public enum MealType
{
Breakfast = 1,
Lunch = 2,
Dinner = 3
}
public string Name { get; set; }
public MealType Type { get; set; }
public int Calories { get; set; }
public decimal Cost { get; set; }
public Meal(string _name, MealType _type, int _calories, decimal _cost)
{
Name = _name;
Type = _type;
Calories = _calories;
Cost = _cost;
}
}
I'm able to read in from a spreadsheet or some other source and create a Meal for each record and add it to a List. I have no idea how to read through my list (or any collection), though, and find the maximum caloric combination of 4 meals while adhering to the requirement that Meal #1 must be of type "breakfast", Meal #2 must be of type "lunch", Meal #3 must be of type "dinner", and Meal #4 can be of type "breakfast" or "lunch". Any ideas on where to begin? Thanks.
UPDATE
I ended up getting this to work - though I'm sure it is not very efficient, especially as the input list grows. Here is the hideous code (I obviously also defined a Menu class not included):
List<Meal> allMeals = Meal.GetMeals();
List<Meal> allBreakfast = new List<Meal>();
List<Meal> allLunch = new List<Meal>();
List<Meal> allDinner = new List<Meal>();
List<Meal> allSnack = new List<Meal>();
foreach (Meal meal in allMeals)
{
if (meal.MealType == Meal.MealType.Breakfast)
{
allBreakfast.Add(meal);
allSnack.Add(meal);
}
else if (meal.MealType == Meal.MealType.Lunch)
{
allLunch.Add(meal);
allSnack.Add(meal);
}
else if (meal.MealType == Meal.MealType.Dinner)
{
allDinner.Add(meal);
}
}
foreach (Meal breakfast in allBreakfast)
{
foreach (Meal lunch in allLunch)
{
foreach (Meal dinner in allDinner)
{
foreach (Meal snack in allSnack)
{
if (snack == breakfast || snack == lunch)
{
continue;
}
currMenu = new Menu(breakfast, lunch, dinner, snack);
if (currMenu.Cost < Menu.MaxCost && (maxMenu == null || currMenu.Calories > maxMenu.Calories))
{
maxMenu = currMenu;
}
}
}
}
}
In this situation, if you are not familiar with complex algorithm, you can try the brute force algo which is still feasible in the time and memory space.
We have 4 sets to store record: A, B, C, D (A: breakfast, B: lunch, C: dinner, D = A + B)
Our aim is to find Ai, Bj, Cu, Dv so as to:
1. Ai.cost + Bj.cost + Cu.cost + Dv.cost <=50
2. Ai.calo + Bj.calo + Cu.calo + Dv.calo is maximum
As in reality, we just count up to 2 decimal--> range of price is from 0.00 -> 50.00, i.e. 5000 case
1. Find all cost combination of Ai + Bj (<=50) store into array AB.
if (Ai1 + Bj1 == Ai2 + Bj2), just take combination have higher calories.
=> Complexity: O(|A| * |B|)
2. Find all cost combination of Cu + Dv (<=50) store into array CD with the same process as for AB
=> Complexity: O(|C| * |D|)
Note: size of AB and CD is 5000 elements
3. Find all combination AB[p] + CD[q] which cost is under 5000 and we can know which one got highest calories.
=> Complexity: O(|AB| * |CD| ) = O(5000 * 5000)
Totally, if using brute-force, the time complexity is O(5000 * 5000) + O(|spreadSheet size for one categories|^2).
If the spreadSheet size is <= 5000 and you are not familiar with algorithm, you can use this simple approach. Otherwise, just move ahead, algorithm is fun :)
Ok, so if you have read about Knapsack problem, and still don't know how to solve it, here is something for you to consider
The idea is simple, the array result contains all the value of money that can be reached by any Meal combination. So at first, when you doesn't buy anything, only result[50] can be reached.
Iterating through all the choices, starting from breakfast to snack, and only check for the value that can be reached, we can slowly build up the value table. For example, if breakfast has three prices(1,2,3) so the next state can only be (50-1), (50-2), (50-3) and so on. In array result, we store the maximum value of calories which can be created. So if one Breakfast set is cost 3, calo 2, and another set is cost 3, calo 4, so the value at index (50 -3) of the result array is 4.
Finally, after scanning through all of the state, the value storing in result array is the maximum value of calories that can be made.
int money = 50;// your initial money
int[] result = new int[money + 1];
for (int i = 0; i < result.length; i++) {
result[i] = -1;//-1 means cannot reach
}
result[50] = 0;//Only 50 can be reached at first
List<Meal>[] list = new List[4]; // Array for all 4 meals
// 0 is Breakfast
// 1 is lunch
// 2 for dinner
// 3 for snack
for (int i = 0; i < 4; i++) {//Iterating from breakfast to snack
int[] temp = new int[money + 1];//A temporary array to store value
for (int j = 0; j < result.length; j++) {
temp[j] = -1;
}
for (int j = 0; j < result.length; j++) {
if (result[j] != -1) {//For previous value, which can be reached
//Try all posibilites
for (int k = 0; k < list[i].size(); k++) {
int cost = list[i].get(k).cost;
int energy = list[i].get(k).energy;
if (cost <= result[j]) {
temp[j - cost] = max(temp[j - cost], result[j] + energy);
}
}
}
}
result = temp;//Need to switch these arrays.
}
int max = 0;
for(int i = 0; i < result.length; i++){
max = max(max, result[i]);
}
System.out.println(max);