I have a list of ~300 objects which all have a price and a score. I need to find the best combination (i.e. highest total score) of 15 of those objects whose total price is less than X.
The most straightforward way to do this, as I see it, is to nest 15 for loops and check every possible combination, but that would take days.
Is there any 'clean' way to do this in C#?
Thanks!
It is difficult to help without an example, but if I understand the problem then this might help.
Assuming your object looks like this
public class Item
{
public int Score { get; set; }
public decimal Price { get; set; }
}
Then the following should sort you out.
var listOfObjects = new List<Item>();
var topItems = listOfObjects.Where(p => p.Price < 100).OrderByDescending(p => p.Score).Take(15);
EDIT : After all details was disclosed, the following should help
DISCLAIMER : Quick and dirty solution (sub optimal)
Create a new class
public class ItemWithRunningTotal
{
public Item Item { get; set; }
public decimal RunningTotal { get; set; }
}
Then the following should get you what you need.
var maxTotal = 1500; //for you 8000
var objects = new List<Item>()
{
new Item() {Score = 10, Price = 100},
new Item() {Score = 20, Price = 800},
new Item() {Score = 40, Price = 600},
new Item() {Score = 5, Price = 300},
};
decimal runningTotal = 0;
var newList = objects
.OrderByDescending(p => p.Score)
.Select(p =>
{
runningTotal = runningTotal + p.Price;
return new ItemWithRunningTotal()
{
Item = p,
RunningTotal = runningTotal
};
})
.OrderByDescending(p => p.RunningTotal)
.Where(p => p.RunningTotal <= maxTotal).Take(15);
What you're looking is solution for knapsack problem. There is no known way to do it fast.
You can modify dynamic solution (https://en.wikipedia.org/wiki/Knapsack_problem#0.2F1_knapsack_problem) to choose at most maxCount items like this:
public static List<Item> BestCombination(List<Item> items, int maxPrice, int maxCount)
{
var scores = new int[items.Count + 1, maxPrice + 1, maxCount + 1];
for (int i = 1; i <= items.Count; i++)
{
var item = items[i - 1];
for (int count = 1; count <= Math.Min(maxCount, i); count++)
{
for (int price = 0; price <= maxPrice; price++)
{
if (item.Price > price)
scores[i, price, count] = scores[i - 1, price, count];
else
scores[i, price, count] = Math.Max(
scores[i - 1, price, count],
scores[i - 1, price - item.Price, count - 1] + item.Score);
}
}
}
var choosen = new List<Item>();
int j = maxPrice;
int k = maxCount;
for (int i = items.Count; i > 0; i--)
{
var item = items[i - 1];
if (scores[i, j, k] != scores[i - 1, j, k])
{
choosen.Add(item);
j -= item.Price;
k--;
}
}
return choosen;
}
Keep in mind that choosing exaclty maxCount objects can give you lower total score than choosing <= maxCount objects. Eg for items [(100, 10), (200,20), (300,30), (500,80)], maxPrice = 500 and maxCount = 2 this method returns only (500,80). If you want it to return [(200,20), (300,30)], you can initialize array like this:
for (int i = 0; i <= items.Count; i++)
{
for (int price = 0; price <= maxPrice; price++)
{
for (int count = 1; count <= maxCount; count++)
{
scores[i, price, count] = int.MinValue;
}
}
}
to make sure that in scores[, , count] are sums of count scores (or MinValue). However, it still can return another number of items if there is no way to choose exactly maxCount.
Related
I have class in C# -
public class Availability
{
public ushort RegionId {get;set;}
}
Then , I have a method GetListOfClassesAsPerIterations(Availability xiAvailability)
I have List in this method where I want to append different values of RegionId.
Eg. First 100 elements in List with RegionId=1500 , next 100 elements with RegionId=1400
But what is happening is , when I add first 100 elements in list with RegionId=1500 , and then try to add next 100 elements in list with RegionId=1400 , all the items in List becomes with RegionId=1400.
I have written code for it as -
var lstAvailability = List<Availability>
for (int i = 0; i < 100; i++)
{
xiAvailability.RegionId = 1500;
lstAvailability.Add(xiAvailability);
}
for (int i = 0; i < 100; i++)
{
xiAvailability.RegionId = 1400;
lstAvailability.Add(xiAvailability);
}
Here I can see all the 200 elements with RegionId as 1400.
But instead , I was expecting first 100 to be with 1500 and next 100 to be with 1400.
What could be the alternative?
This is because you are putting 200 references to the same object in the list. If you want to have 200 different objects, then you need to do something like this:
var lstAvailability = new List<Availability>();
for (int i = 0; i < 200; i++)
{
var xiAvailability = new Availability();
if (i < 100)
xiAvailability.RegionId = 1400;
else
xiAvailability.RegionId = 1500;
lstAvailability.Add(xiAvailability);
}
If you need to have 100 references to 2 objects, then do this
var lstAvailability = new List<Availability>();
var xiAvailability = new Availability() { RegionId=1400 };
for (int i = 0; i < 200; i++)
{
if (i == 100)
xiAvailability = new Availability() { RegionId = 1500 };
lstAvailability.Add(xiAvailability);
}
Object in the list is a reference to the actual object. Hence, you're seeing the latest value of 1400 for RegionId.
You can try the below,
var xiAvailability1500 = new Availability();
xiAvailability1500.RegionId = 1500;
var xiAvailability1400 = new Availability();
xiAvailability1400.RegionId = 1400;
var lstAvailability = List<Availability>();
for (int i = 0; i < 200; i++)
{
if (i < 100)
lstAvailability.Add(xiAvailability1500);
else
lstAvailability.Add(xiAvailability1400);
}
Here is another alternative way:
var lstAvailability = List<Availability>
int count = 0;
for (int i = 0; i < 200; i++)
{
if(count => 0 && count <= 100)
{
xiAvailability.RegionId = 1500;
lstAvailability.Add(xiAvailability);
}
if (count => 100)
{
xiAvailability.RegionId = 1400;
lstAvailability.Add(xiAvailability);
}
count = count + 1;
}
I am trying to select 15 events with each iteration of i.
In the first loop of i, I want to add validbatch[0 to 15].
Next loop validbatch[15 to 30] and so on.
How do I filter or select a subarray from valid batch where the index > i*15?
for (j = counter; j < i * 15; j++)
{
crm x = new crm();
x.EmailAddress = EMAILaddress[j];
Properties prop = new Properties();
prop.new_insideroptout = optin[j];
validBatch[j] = new SampleEventBody() { Id = Int64.Parse(ID[j]), Publication = subscriptionname[j], CrmProperties = x, Properties = prop };
counter++;
}
sendTasks.Add(client.SendEventBatchAsync(validBatch.Where<EventBody>(validbatch => validBatch[j] > validbatch[i * 15 - 1])
You could use the following generic method to split an array.
public static IEnumerable<IEnumerable<T>> SplitArray<T>(this T[] array, int size)
{
for (var i = 0; i < (float)array.Length / size; i++)
{
yield return array.Skip(i * size).Take(size);
}
}
And then you can call it like
var splitedArray = array.SplitArray(2);
I hope you are looking for this.
First off yes, This is a homework assignment, I've been struggling with it for 3 days, and I can't figure it out.
Basically the problem is to take a decimal amount entered by the user in a text box, I then need to take that number and break it down into currency denominations, $50, $20, $10, $5, $1 and if the amount has a decimal then into
$.25, $.10, $.05, $.01.
And I need to break this down in the lowest amount of denominations possible, for example $100 would be broken down into 2 $50 bills.
Here is what I have so far.
private void btnDispense_Click(object sender, EventArgs e)
{
decimal i;
i = decimal.Parse(txtAmountReq.Text);
decimal totalAmount = Convert.ToDecimal(txtAmountReq);
int[] denomBills = { 50, 20, 10, 5, 1 };
int[] numberOfBills = new int[5];
decimal[] denomCoins = { .25m, .10m, .05m, .01m };
int[] numberOfCoins = new int[4];
//For loop for amount of bills
for (numberOfBills[0] = 0; totalAmount >= 50; numberOfBills[0]++)
{
totalAmount = totalAmount - 50;
}
for (numberOfBills[1] = 0; totalAmount < 20; numberOfBills[1]++)
{
totalAmount = totalAmount - 20;
}
for (numberOfBills[2] = 0; totalAmount < 10; numberOfBills[2]++)
{
totalAmount = totalAmount - 10;
}
for (numberOfBills[3] = 0; totalAmount < 5; numberOfBills[3]++)
{
totalAmount = totalAmount - 5;
}
for (numberOfBills[4] = 0; totalAmount <= 0; numberOfBills[4]++)
{
totalAmount = totalAmount - 1;
}
//For loop for the amount of coins
for (numberOfCoins[0] = 0; totalAmount >= .25m; numberOfBills[0]++)
{
totalAmount = totalAmount - .25m;
}
for (numberOfBills[1] = 0; totalAmount < .10m; numberOfBills[1]++)
{
totalAmount = totalAmount - .10m;
}
for (numberOfBills[2] = 0; totalAmount < .05m; numberOfBills[2]++)
{
totalAmount = totalAmount - .05m;
}
for (numberOfBills[3] = 0; totalAmount < .01m; numberOfBills[3]++)
{
totalAmount = totalAmount - .01m;
}
txt50.Text = Convert.ToString(numberOfBills[0]);
txt20.Text = Convert.ToString(numberOfBills[1]);
txt10.Text = Convert.ToString(numberOfBills[2]);
txt5.Text = Convert.ToString(numberOfBills[3]);
txt1.Text = Convert.ToString(numberOfBills[4]);
txtQuarter.Text = Convert.ToString(numberOfCoins[0]);
txtDime.Text = Convert.ToString(numberOfCoins[1]);
txtNickel.Text = Convert.ToString(numberOfCoins[2]);
txtPenny.Text = Convert.ToString(numberOfCoins[3]);
}
Any help would be greatly appreciated.
This is very famous knapsack type problem.You might want to start exploring from here : https://en.wikipedia.org/wiki/Change-making_problem
The best way to address this problem is to find all possible combinations of change and then find the optimal solution.
Below is the code by karamana I found on codeproject.com :
//find all the combinations
private void findAllCombinationsRecursive(String tsoln,
int startIx,
int remainingTarget,
CoinChangeAnswer answer) {
for(int i=startIx; i<answer.denoms.length ;i++) {
int temp = remainingTarget - answer.denoms[i];
String tempSoln = tsoln + "" + answer.denoms[i]+ ",";
if(temp < 0) {
break;
}
if(temp == 0) {
// reached the answer hence quit from the loop
answer.allPossibleChanges.add(tempSoln);
break;
}
else {
// target not reached, try the solution recursively with the
// current denomination as the start point.
findAllCombinationsRecursive(tempSoln, i, temp, answer);
}
}
}
in order to find optimum solution :
public CoinChangeAnswer findOptimalChange(int target, int[] denoms) {
CoinChangeAnswer soln = new CoinChangeAnswer(target,denoms);
StringBuilder sb = new StringBuilder();
// initialize the solution structure
for(int i=0; i<soln.OPT[0].length ; i++) {
soln.OPT[0][i] = i;
soln.optimalChange[0][i] = sb.toString();
sb.append(denoms[0]+" ");
}
// Read through the following for more details on the explanation
// of the algorithm.
// http://condor.depaul.edu/~rjohnson/algorithm/coins.pdf
for(int i=1 ; i<denoms.length ; i++) {
for(int j=0; j<target+1 ; j++) {
int value = j;
int targetWithPrevDenomiation = soln.OPT[i-1][j];
int ix = (value) - denoms[i];
if( ix>=0 && (denoms[i] <= value )) {
int x2 = denoms[i] + soln.OPT[i][ix];
if(x2 <= target && (1+soln.OPT[i][ix] < targetWithPrevDenomiation)) {
String temp = soln.optimalChange[i][ix] + denoms[i] + " ";
soln.optimalChange[i][j] = temp;
soln.OPT[i][j] = 1 + soln.OPT[i][ix];
} else {
soln.optimalChange[i][j] = soln.optimalChange[i-1][j]+ " ";
soln.OPT[i][j] = targetWithPrevDenomiation;
}
} else {
soln.optimalChange[i][j] = soln.optimalChange[i-1][j];
soln.OPT[i][j] = targetWithPrevDenomiation;
}
}
}
return soln;
}
Link to the original code here
I meet a problem that I don't know how to solve it. I created an 2-D array which contains Date and price.I want to delete the row whose datetime is between two date.The example below, I want to delete the third row.
Date Price
01/07 10
02/07 20
Empty 30
03/07 40
Here is my code:(I dont know why it does work)
for (int i=0;i<row.length;i++)
{
for (int j=0;j<col.length;j++)
{
if (Array[row,0]=" ")
{
Array[row,j]=Array[row+1,j];
i++
}
}
}
If I were you I would create an object and store Date and Price as a properties.
For example:
public class DateAndPrice //let this name be whatever you want
{
public DateTime Date { get; set; }
public int Price { get; set; }
}
Then, store them in a List so you can easily remove them with the Remove method.
List<DateAndPrice> list = new List<DateAndPrice>();
If you are hell-bound on using arrays, you can use a Linq query to filter out the results and return a new array:
var data = new[] {
new { Date = "01/07", Price = 10 },
new { Date = "02/07", Price = 20 },
new { Date = "", Price = 30 },
new { Date = "03/07", Price = 40 }
};
var noBlanks = (from d in data
where !string.IsNullOrWhiteSpace(d.Date)
select d).ToArray();
Which will select the data that does not have empty, null, or whitespace date items and place them in a new array.
If you're dead set on staying with a non-List like 2D array, you can try the following:
string[,] array =
{
{ "01/07", "10" },
{ "02/07", "20" },
{ String.Empty, "30" },
{ "03/07", "40" },
};
array = RemoveEmptyDates(array);
for (int i = 0; i <= array.GetUpperBound(0); i++)
{
for (int j = 0; j <= array.GetUpperBound(1); j++)
{
Console.Write("{0} \t", array[i, j]);
}
Console.WriteLine();
}
RemoveEmptyDates looks like:
public static string[,] RemoveEmptyDates(string[,] array)
{
// Find how many rows have an empty date
int rowsToRemove = 0;
for (int i = 0; i <= array.GetUpperBound(0); i++)
{
if (string.IsNullOrEmpty(array[i, 0]))
{
rowsToRemove++;
}
}
// Reinitialize an array minus the number of empty date rows
string[,] results = new string[array.GetUpperBound(0) + 1 - rowsToRemove, array.GetUpperBound(1) + 1];
int row = 0;
for (int i = 0; i <= array.GetUpperBound(0); i++)
{
int col = 0;
if (!string.IsNullOrEmpty(array[i, 0]))
{
for (int j = 0; j <= array.GetUpperBound(1); j++)
{
results[row, col] = array[i, j];
col++;
}
row++;
}
}
return results;
}
Results:
01/07 10
02/07 20
03/07 40
Your approach to this is not the best. You should be creating a helper class:
public class DatePrice
{
public DateTime Date { get; set; }
public decimal Price { get; set; }
}
Then creating a collection class:
var prices = new List<DatePrice>();
Then you can add data like this:
prices.Add(new DatePrice() { Date = DateTime.Now, Price = 10m });
And you can easily remove an item based on an index like this:
prices.RemoveAt(2);
If you really must use an array, you'll need an extension method, such as this to remove an item (copied from here):
public static T[] RemoveAt<T>(this T[] source, int index)
{
T[] dest = new T[source.Length - 1];
if( index > 0 )
Array.Copy(source, 0, dest, 0, index);
if( index < source.Length - 1 )
Array.Copy(source, index + 1, dest, index, source.Length - index - 1);
return dest;
}
For 2-dimensional arrays, use this:
string[][] a = new string[][] {
new string[] { "a", "b" } /*1st row*/,
new string[] { "c", "d" } /*2nd row*/,
new string[] { "e", "f" } /*3rd row*/
};
int rowToRemove = 1; // 2nd row
a = a.Where((el, i) => i != rowToRemove).ToArray();
I have an array of players (string[]) and now I need to get an array of pairs representing games (playerN-playerM) to orginize tournament table like at this picture:
The desired end result is to generate a fixture list with all the games the need to be played.
How can I do this with LINQ in efficient way?
UPDATED:
A-B, A-C, A-D is not correct - games should be able to run in parallel.
I need result in the same order as at the picture
The following code can be used to generate a fixture list for a collection of teams to ensure that each time plays all other teams in 1 home and 1 away match.
The code is a bit long winded but it does work by providing you with a list in the order you have specified.
The code can probably be optimised but at the moment this is how it has come from my head.
NOTE: The resulting list will contain both Home and Away fixture, which based on your grid will be what you need to do anyway.
class Fixture
{
public string Home { get; set; }
public string Away { get; set; }
}
void CallCode()
{
string players = new string[] { "A", "B", "C", "D" };
List<Fixture> fixtures = CalculateFixtures(players);
}
List<Fixture> CalculateFixtures(string[] players)
{
//create a list of all possible fixtures (order not important)
List<Fixture> fixtures = new List<Fixture>();
for (int i = 0; i < players.Length; i++)
{
for (int j = 0; j < players.Length; j++)
{
if (players[i] != players[j])
{
fixtures.Add(new Fixture() { Home = players[i], Away = players[j] });
}
}
}
fixtures.Reverse();//reverse the fixture list as we are going to remove element from this and will therefore have to start at the end
//calculate the number of game weeks and the number of games per week
int gameweeks = (players.Length - 1) * 2;
int gamesPerWeek = gameweeks / 2;
List<Fixture> sortedFixtures = new List<Fixture>();
//foreach game week get all available fixture for that week and add to sorted list
for (int i = 0; i < gameweeks; i++)
{
sortedFixtures.AddRange(TakeUnique(fixtures, gamesPerWeek));
}
return sortedFixtures;
}
List<Fixture> TakeUnique(List<Fixture> fixtures, int gamesPerWeek)
{
List<Fixture> result = new List<Fixture>();
//pull enough fixture to cater for the number of game to play
for (int i = 0; i < gamesPerWeek; i++)
{
//loop all fixture to find an unused set of teams
for (int j = fixtures.Count - 1; j >= 0; j--)
{
//check to see if any teams in current fixtue have already been used this game week and ignore if they have
if (!result.Any(r => r.Home == fixtures[j].Home || r.Away == fixtures[j].Home || r.Home == fixtures[j].Away || r.Away == fixtures[j].Away))
{
//teams not yet used
result.Add(fixtures[j]);
fixtures.RemoveAt(j);
}
}
}
return result;
}
var games = players.SelectMany((player1, index) =>
players.Skip(index + 1).
Select(player2 => new {Player1 = player1, Player2 = player2}));
That should do it...
The implementation I really wanted:
public static List<List<Tuple<string, string>>> ListMatches(List<string> listTeam)
{
var result = new List<List<Tuple<string, string>>>();
int numDays = (listTeam.Count - 1);
int halfSize = listTeam.Count / 2;
var teams = new List<string>();
teams.AddRange(listTeam.Skip(halfSize).Take(halfSize));
teams.AddRange(listTeam.Skip(1).Take(halfSize - 1).ToArray().Reverse());
int teamsSize = teams.Count;
for (int day = 0; day < numDays; day++)
{
var round = new List<Tuple<string, string>>();
int teamIdx = day % teamsSize;
round.Add(new Tuple<string, string>(teams[teamIdx], listTeam[0]));
for (int idx = 1; idx < halfSize; idx++)
{
int firstTeam = (day + idx) % teamsSize;
int secondTeam = (day + teamsSize - idx) % teamsSize;
round.Add(new Tuple<string, string>(teams[firstTeam], teams[secondTeam]));
}
result.Add(round);
}
return result;
}