pick between several actions using percentage - c#

I have an Object with X amount of hobbies, each hobby has a value between 0-100 (representing %), and together they add up to 100.
Every morning I want to run a function that decides what that Object is going to do, and the higher value a hobby has, the more likely they are to be chosen.
I cannot figure out how to translate that last part into code. Using these variables for example:
int fishing = 25;
int sleeping = 25;
int drinking = 50;
int running = 0;

This will work for you:
class Program
{
private static List<KeyValuePair<string, int>> Actions = new List<KeyValuePair<string, int>>()
{
new KeyValuePair<string, int>("fishing", 25),
new KeyValuePair<string, int>("sleeping", 25),
new KeyValuePair<string, int>("drinking", 5),
new KeyValuePair<string, int>("running", 0),
};
static void Main(string[] args)
{
var result = Actions.OrderByDescending(r => r.Value).First();
Console.WriteLine(result.Key);
}
}
This assumes that you already have the actions and their percentage. Now if any 2 (or more) of the items have the same percentage, it will show the first one added to the list.
EDIT
Sorry didn't get the requirement for randomization. Try this:
var listOfActionsToTake = new List<string>() { "fishing", "sleeping", "drinking", "running" };
var listOFActions = new List<KeyValuePair<string, int>>();
var rand = new Random();
var currentPercentage = 101;
for (int i = 0; i < listOfActionsToTake.Count; i++)
{
var current = rand.Next(currentPercentage);
if (i == listOfActionsToTake.Count - 1)
{
current = currentPercentage - 1;
}
listOFActions.Add(new KeyValuePair<string, int>(listOfActionsToTake[i], current));
currentPercentage -= current;
}
foreach (var action in listOFActions)
{
Console.WriteLine($"{action.Key}: {action.Value}");
}
var result = listOFActions.OrderByDescending(r => r.Value).First();
Console.WriteLine(result.Key);
Like this, no matter how many values you add to the listOfActionsToTake you will always end up with a sum of 100 between them, and the biggest will be selected

I found this example and it seems to make sense. You add the values to get a cumulative value. If the random value created is less then the cumulative probability, then the current item is the selected.
Basically, if that condition is false, then you ignore the current items probability from the condition. So the next item to be compared now has a higher probability to be chosen.
For example:
// 1. RandNum = 0.4
// 2. cumulative = 0.0
// 3. fishing = 0.25
// 4. cumulative = cumulative + fishing = 0.25
// 5. RandNum < cumulative ? false
The random number was 0.4 and fishing has a probably of 0.25 to be selected. Since fishing's probability is lower then the random number, we add it to the cumulative value and essentially ignore it in the next item. The next item will now have a higher probability to be chosen because now it's just sleeping and drinking. They now both have a .50 probability to be chosen.
// 1. RandNum = 0.4
// 2. cumulative = 0.25
// 3. sleeping = 0.25
// 4. cumulative = cumulative + sleeping = .50
// 5. RanNum < cumulative ? true
Although you would have to modify it to account for when the percentages are equal since it'll just take the first one. You could check if the percentages are the same after finding an inital action to do. If the randomly chosen action has the same percentage as another, then randomly pick one of them.
static void Main(string[] args)
{
int fishing = 25;
int sleeping = 25;
int drinking = 50;
int running = 0;
List<KeyValuePair<string, double>> elements = new List<KeyValuePair<string, double>>();
elements.Add(new KeyValuePair<string, double>("fishing", (fishing * (1.0 / 100.0)))); // 25 * (1 /100) = .25
elements.Add(new KeyValuePair<string, double>("sleeping", (sleeping * (1.0 / 100.0))));
elements.Add(new KeyValuePair<string, double>("drinking", (drinking * (1.0 / 100.0))));
elements.Add(new KeyValuePair<string, double>("running", (running * (1.0 / 100.0))));
Random r = new Random();
double rand = r.NextDouble();
double cumulative = 0.0;
string selectedElement = "";
for (int i = 0; i < elements.Count; i++)
{
cumulative += elements[i].Value;
if (rand < cumulative)
{
selectedElement = elements[i].Key;
break;
}
}
Console.WriteLine("Selected Element: " + selectedElement);
Console.ReadKey();
}
// Test 1: rand: 0.522105917
// cumulative: 1
// Selected Element: drinking
// Test 2: rand: 0.49201479
// cumulative: 0.5
// Selected Element: sleeping

This should do it and will work when they don't add to 100 too.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static Dictionary<string, int> activities = new Dictionary<string, int>
{
{ "running", 0 },
{ "fishing", 25 },
{ "sleeping", 25 },
{ "drinking", 50 }
};
static void Main(string[] args)
{
int sum = activities.Sum(a => a.Value);
int rand = new Random().Next(sum);
int total = -1;
string activity = activities.SkipWhile(a => (total += a.Value) < rand).First().Key;
Console.WriteLine(activity);
}
}

Related

Calculate max on a sliding window for TimeSeries

Input:
public class MyObject
{
public double Value { get; set; }
public DateTime Date { get; set; }
}
Method to generate test objects:
public static MyObject[] GetTestObjects()
{
var rnd = new Random();
var date = new DateTime(2021, 1, 1, 0, 0, 0);
var result = new List<MyObject>();
for (int i = 0; i < 50000; i++)
{
//this is to simulate real data having gaps
if (rnd.Next(100) < 25)
{
continue;
}
var myObject = new MyObject()
{
Value = rnd.NextDouble(),
Date = date.AddMinutes(15 * i)
};
result.Add(myObject);
}
return result.ToArray();
}
Given this I require to calculate maximum Value for previous 12 month for each myObject. I could just think of doing this InParallel, but maybe there is an optimized solution?
Sorry for being unclear, this is what I use right now to get what I want:
public MyObject[] BruteForceBackward(MyObject[] testData)
{
return testData.AsParallel().Select(point =>
{
var max = testData.Where(x => x.Date <= point.Date && x.Date >= point.Date.AddYears(-1)).Max(x => x.Value);
return new MyObject() { Date = point.Date, Value = point.Value / max };
}).OrderBy(r => r.Date).ToArray();
}
This works but it is slow and eats processor resources (imagine, you have 100k objects), I believe there must be something better
I had a simillar project where i had to calculate such stuff on tons of sensor data.
You can now find a little more refined version in my Github repository, which should be ready to use (.Net):
https://github.com/forReason/Statistics-Helper-Library
In general you want to reduce the amount of loops going over all your data. At best, you want to touch each element only one single time.
Process Array (equiv. of BruteForceBackwards)
public static MyObject[] FlowThroughForward(ref MyObject[] testData)
{
// generate return array
MyObject[] returnData = new MyObject[testData.Length];
// keep track to minimize processing
double currentMaximum = 0;
List<MyObject> maximumValues = new List<MyObject>();
// go through the elements
for (int i = 0; i < testData.Length; i++)
{
// calculate the oldest date to keep in tracking list
DateTime targetDate = testData[i].Date.AddYears(-1);
// maximum logic
if (testData[i].Value >= currentMaximum)
{
// new maximum found, clear tracking list
// this is the best case scenario
maximumValues.Clear();
currentMaximum = testData[i].Value;
}
else
{
// unfortunately, no new maximum was found
// go backwards the maximum tracking list and check for smaller values
// clear the list of all smaller values. The list should therefore always
// be in descending order
for (int b = maximumValues.Count - 1; b >= 0; b--)
{
if (maximumValues[b].Value <= testData[i].Value)
{
// a lower value has been found. We have a newer, higher value
// clear this waste value from the tracking list
maximumValues.RemoveAt(b);
}
else
{
// there are no more lower values.
// stop looking for smaller values to save time
break;
}
}
}
// append new value to tracking list, no matter if higher or lower
// all future values might be lower
maximumValues.Add(testData[i]);
// check if the oldest value is too old to be kept in the tracking list
while (maximumValues[0].Date < targetDate)
{
// oldest value is to be removed
maximumValues.RemoveAt(0);
// update maximum
currentMaximum = maximumValues[0].Value;
}
// add object to result list
returnData[i] = new MyObject() { Date = testData[i].Date, Value = testData[i].Value / currentMaximum }; ;
}
return returnData;
}
Real Time Data or Streamed Data
Note: If you have really large lists, you might get memory issues with your approach to pass a full array. In this case: pass one value at a time, pass them from oldest value to newest value. Store the values back one at a time.
This Function can also be used on real time data.
The test method is included in code.
static void Main(string[] args)
{
int length = 50000;
Stopwatch stopWatch1 = new Stopwatch();
stopWatch1.Start();
var myObject = new MyObject();
var result = new List<MyObject>();
var date = new DateTime(2021, 1, 1, 0, 0, 0);
for (int i = 0; i < length; i++)
{
//this is to simulate real data having gaps
if (rnd.Next(100) < 25)
{
continue;
}
myObject.Value = rnd.NextDouble();
myObject.Date = date.AddMinutes(15 * i);
result.Add(CalculateNextObject(ref myObject));
}
stopWatch1.Stop();
Console.WriteLine("test code executed in " + stopWatch1.ElapsedMilliseconds + " ms");
Thread.Sleep(1000000);
}
private static Random rnd = new Random();
private static double currentMaximum = 0;
private static List<MyObject> maximumValues = new List<MyObject>();
public static MyObject CalculateNextObject(ref MyObject input)
{
// calculate the oldest date to keep in tracking list
DateTime targetDate = input.Date.AddYears(-1);
// maximum logic
if (input.Value >= currentMaximum)
{
// new maximum found, clear tracking list
// this is the best case scenario
maximumValues.Clear();
currentMaximum = input.Value;
}
else
{
// unfortunately, no new maximum was found
// go backwards the maximum tracking list and check for smaller values
// clear the list of all smaller values. The list should therefore always
// be in descending order
for (int b = maximumValues.Count - 1; b >= 0; b--)
{
if (maximumValues[b].Value <= input.Value)
{
// a lower value has been found. We have a newer, higher value
// clear this waste value from the tracking list
maximumValues.RemoveAt(b);
}
else
{
// there are no more lower values.
// stop looking for smaller values to save time
break;
}
}
}
// append new value to tracking list, no matter if higher or lower
// all future values might be lower
maximumValues.Add(input);
// check if the oldest value is too old to be kept in the tracking list
while (maximumValues[0].Date < targetDate)
{
// oldest value is to be removed
maximumValues.RemoveAt(0);
// update maximum
currentMaximum = maximumValues[0].Value;
}
// add object to result list
MyObject returnData = new MyObject() { Date = input.Date, Value = input.Value / currentMaximum };
return returnData;
}
Test Method
static void Main(string[] args)
{
MyObject[] testData = GetTestObjects();
Stopwatch stopWatch1 = new Stopwatch();
Stopwatch stopWatch2 = new Stopwatch();
stopWatch1.Start();
MyObject[] testresults1 = BruteForceBackward(testData);
stopWatch1.Stop();
Console.WriteLine("BruteForceBackward executed in " + stopWatch1.ElapsedMilliseconds + " ms");
stopWatch2.Start();
MyObject[] testresults2 = FlowThroughForward(ref testData);
stopWatch2.Stop();
Console.WriteLine("FlowThroughForward executed in " + stopWatch2.ElapsedMilliseconds + " ms");
Console.WriteLine();
Console.WriteLine("Comparing some random test results: ");
var rnd = new Random();
for (int i = 0; i < 10; i++)
{
int index = rnd.Next(0, testData.Length);
Console.WriteLine("Index: " + index + " brute: " + testresults1[index].Value + " flow: " + testresults2[index].Value);
}
Thread.Sleep(1000000);
}
Test result
Tests were performed on a machine with 32 cores, so in teory multithreaded aproach should be at advantage but youll see ;)
Function
Function Time
time %
BruteForceBackward
5334 ms
99.9%
FlowThroughForward
5 ms
0.094%
Performance improvement factor: ~time/1000
console output with data validation:
BruteForceBackward executed in 5264 ms
FlowThroughForward executed in 5 ms
Comparing some random test results:
Index: 25291 brute: 0.989688139105413 flow: 0.989688139105413
Index: 11945 brute: 0.59670821976193 flow: 0.59670821976193
Index: 30282 brute: 0.413238225210297 flow: 0.413238225210297
Index: 33898 brute: 0.38258761939139 flow: 0.38258761939139
Index: 8824 brute: 0.833512217105447 flow: 0.833512217105447
Index: 22092 brute: 0.648052464067263 flow: 0.648052464067263
Index: 24633 brute: 0.35859417692481 flow: 0.35859417692481
Index: 24061 brute: 0.540642018793402 flow: 0.540642018793402
Index: 34219 brute: 0.498785766613022 flow: 0.498785766613022
Index: 2396 brute: 0.151471808392111 flow: 0.151471808392111
Cpu usage was a lot higher on Bruteforce backwards due to parallelisation.
The worst case scenario are long periods of decreasing values. The code can still be vastly optimized but I guess this should be sufficient. For further optimisation, one might look to reduce the list shuffles when removing/adding elements to maximumValues.
An interesting and challenging problem. I put together a solution using a dynamic programming approach (first learned back in CS algorithms class back in '78). First, a tree is constructed containing pre-calculated local max values over recursively defined ranges. Once constructed, the max value for an arbitrary range can be efficiently calculated mostly using the pre-calculated values. Only at the fringes of the range does the calculation drop down to the element level.
It is not as fast as julian bechtold's FlowThroughForward method, but random access to ranges may be a plus.
Code to add to Main:
Console.WriteLine();
Stopwatch stopWatch3 = new Stopwatch();
stopWatch3.Start();
MyObject[] testresults3 = RangeTreeCalculation(ref testData, 10);
stopWatch3.Stop();
Console.WriteLine($"RangeTreeCalculation executed in {stopWatch3.ElapsedMilliseconds} ms");
... test comparison
Console.WriteLine($"Index: {index} brute: {testresults1[index].Value} flow: {testresults2[index].Value} rangeTree: {testresults3[index].Value}");
Test function:
public static MyObject[] RangeTreeCalculation(ref MyObject[] testDataArray, int partitionThreshold)
{
// For this implementation, we need to convert the Array to an ArrayList, because we need a
// reference type object that can be shared.
List<MyObject> testDataList = testDataArray.ToList();
// Construct a tree containing recursive collections of pre-calculated values
var rangeTree = new RangeTree(testDataList, partitionThreshold);
MyObject[] result = new MyObject[testDataList.Count];
Parallel.ForEach(testDataList, (item, state, i) =>
{
var max = rangeTree.MaxForDateRange(item.Date.AddYears(-1), item.Date);
result[i] = new MyObject() { Date = item.Date, Value = item.Value / max };
});
return result;
}
Supporting class:
// Class used to divide and conquer using dynamic programming.
public class RangeTree
{
public List<MyObject> Data; // This reference is shared by all members of the tree
public int Start { get; } // Index of first element covered by this node.
public int Count { get; } // Number of elements covered by this node.
public DateTime FirstDateTime { get; }
public DateTime LastDateTime { get; }
public double MaxValue { get; } // Pre-calculated max for all elements covered by this node.
List<RangeTree> ChildRanges { get; }
// Top level node constructor
public RangeTree(List<MyObject> data, int partitionThreshold)
: this(data, 0, data.Count, partitionThreshold)
{
}
// Child node constructor, which covers an recursively decreasing range of element.
public RangeTree(List<MyObject> data, int start, int count, int partitionThreshold)
{
Data = data;
Start = start;
Count = count;
FirstDateTime = Data[Start].Date;
LastDateTime = Data[Start + Count - 1].Date;
if (count <= partitionThreshold)
{
// If the range is smaller than the threshold, just calculate the local max
// directly from the items. No child ranges are defined.
MaxValue = Enumerable.Range(Start, Count).Select(i => Data[i].Value).Max();
}
else
{
// We still have a significant range. Decide how to further divide them up into sub-ranges.
// (There may be room for improvement here to better balance the tree.)
int partitionSize = (count - 1) / partitionThreshold + 1;
int partitionCount = (count - 1) / partitionSize + 1;
if (count < partitionThreshold * partitionThreshold)
{
// When one away from leaf nodes, prefer fewer full leaf nodes over more
// less populated leaf nodes.
partitionCount = (count - 1) / partitionThreshold + 1;
partitionSize = (count - 1) / partitionCount + 1;
}
ChildRanges = Enumerable.Range(0, partitionCount)
.Select(partitionNum => new {
ChildStart = Start + partitionNum * partitionSize,
ChildCount = Math.Min(partitionSize, Count - partitionNum * partitionSize)
})
.Where(part => part.ChildCount > 0) // Defensive
.Select(part => new RangeTree(Data, part.ChildStart, part.ChildCount, partitionThreshold))
.ToList();
// Now is the dynamic programming part:
// Calculate the local max as the max of all child max values.
MaxValue = ChildRanges.Max(chile => chile.MaxValue);
}
}
// Get the max value for a given range of dates withing this rangeTree node.
// This used the precalculated values as much as possible.
// Only at the fringes of the date range to we calculate at the element level.
public double MaxForDateRange(DateTime fromDate, DateTime thruDate)
{
double calculatedMax = Double.MinValue;
if (fromDate > this.LastDateTime || thruDate < this.FirstDateTime)
{
// Entire range is excluded. Nothing of interest here folks.
calculatedMax = Double.MinValue;
}
else if (fromDate <= this.FirstDateTime && thruDate >= this.LastDateTime)
{
// Entire range is included. Use the already-calculated max.
calculatedMax = this.MaxValue;
}
else if (ChildRanges != null)
{
// We have child ranges. Recurse and accumulate.
// Possible optimization: Calculate max for middle ranges first, and only bother
// with extreme partial ranges if their local max values exceed the preliminary result.
for (int i = 0; i < ChildRanges.Count; ++i)
{
double childMax = ChildRanges[i].MaxForDateRange(fromDate, thruDate);
if (childMax > calculatedMax)
{
calculatedMax = childMax;
}
}
}
else
{
// Leaf range. Loop through just this limited range of notes, checking individually for
// date in range and accumulating the result.
for (int i = 0; i < this.Count; ++i)
{
var element = Data[this.Start + i];
if (fromDate <= element.Date && element.Date <= thruDate && element.Value > calculatedMax)
{
calculatedMax = element.Value;
}
}
}
return calculatedMax;
}
}
There's plenty of room for improvement, such as parameterizing the types and generalizing the functionality to support more than just Max(Value), but the framework is there.
Assuming you meant you need the maximum Value for each of the last 12 months from result, then you can use LINQ:
var beginDateTime = DateTime.Now.AddMonths(-12);
var ans = result.Where(r => r.Date >= beginDateTime).GroupBy(r => r.Date.Month).Select(mg => mg.MaxBy(r => r.Value)).ToList();
Running some timing, I get that putting AsParallel after result changes the run time from around 16ms (first run) to around 32ms, so it is actually slower. It is about the same after the Where and about 23ms after the GroupBy (processing the 12 groups in parallel). On my PC at least, there isn't enough data or complex operations for parallelism, but the GroupBy isn't the most efficient.
Using an array and testing each element, I get the results in about 1.2ms:
var maxMOs = new MyObject[12];
foreach (var r in result.Where(r => r.Date >= beginDateTime)) {
var monthIndex = r.Date.Month-1;
if (maxMOs[monthIndex] == null || r.Value > maxMOs[monthIndex].Value)
maxMOs[monthIndex] = r;
}
Note that the results are not chronological; you could offset monthIndex by today's month to order the results if desired.
var maxMOs = new MyObject[12];
var offset = DateTime.Now.Month-11;
foreach (var r in result.Where(r => r.Date >= beginDateTime)) {
var monthIndex = r.Date.Month-offset;
if (maxMOs[monthIndex] == null || r.Value > maxMOs[monthIndex].Value)
maxMOs[monthIndex] = r;
}
A micro-optimization (mostly useful on repeat runnings) is to invert the test and use the null-propagating operator:
if (!(r.Value <= maxMOs[monthIndex]?.Value))
This saves about 0.2ms on the first run but up to 0.5ms on subsequent runs.
Here is a solution similar to julian bechtold's answer. Difference is that the maximum (and all related variables) are kept hidden away from the main implementation, in a separate class whose purpose is solely to keep track of the maximum over the past year. Algorithm is the same, I just use a few Linq expressions here and there.
We keep track of the maximum in the following class:
public class MaxSlidingWindow
{
private readonly List<MyObject> _maximumValues;
private double _max;
public MaxSlidingWindow()
{
_maximumValues = new List<MyObject>();
_max = double.NegativeInfinity;
}
public double Max => _max;
public void Add(MyObject myObject)
{
if (myObject.Value >= _max)
{
_maximumValues.Clear();
_max = myObject.Value;
}
else
{
RemoveValuesSmallerThan(myObject.Value);
}
_maximumValues.Add(myObject);
RemoveObservationsBefore(myObject.Date.AddYears(-1));
_max = _maximumValues[0].Value;
}
private void RemoveObservationsBefore(DateTime targetDate)
{
var toRemoveFromFront = 0;
while (_maximumValues[toRemoveFromFront].Date < targetDate && toRemoveFromFront <= maximumValues3.Count -1)
{
toRemoveFromFront++;
}
_maximumValues.RemoveRange(0, toRemoveFromFront);
}
private void RemoveValuesSmallerThan(double targetValue)
{
var maxEntry = _maximumValues.Count - 1;
var toRemoveFromBack = 0;
while (toRemoveFromBack <= maxEntry && _maximumValues[maxEntry - toRemoveFromBack].Value <= targetValue)
{
toRemoveFromBack++;
}
_maximumValues.RemoveRange(maxEntry - toRemoveFromBack + 1, toRemoveFromBack);
}
}
It can be used as follows:
public static MyObject[] GetTestObjects_MaxSlidingWindow()
{
var rnd = new Random();
var date = new DateTime(2021, 1, 1, 0, 0, 0);
var result = new List<MyObject>();
var maxSlidingWindow = new MaxSlidingWindow();
for (int i = 0; i < 50000; i++)
{
//this is to simulate real data having gaps
if (rnd.Next(100) < 25)
{
continue;
}
var myObject = new MyObject()
{
Value = rnd.NextDouble(),
Date = date.AddMinutes(15 * i)
};
maxSlidingWindow.Add(myObject);
var max = maxSlidingWindow.Max;
result.Add(new MyObject { Date = myObject.Date, Value = myObject.Value / max });
}
return result.ToArray();
}
See the relative timings below - above solution is slightly faster (timed over 10 million runs), but barely noticeable:
Relative timings

Ensure prize pool doesn't award tied participants less than participants who scored worse [duplicate]

This question already has answers here:
Divide x into y parts by decreasing amount
(3 answers)
Closed 3 years ago.
If I had $1000(variable) and I want to split that amount up and give it to 20(variable) people, but rather than give it evenly to each person, I want to give more to the 1st person, and the 2nd person, etc.
So the 20th person gets the least, and the 5th person gets the 5th most.
People are sorted into a list by score, how could i check to make sure people with the same score are awarded the same amount of the prize while still giving out the prize total to all people?
Formula thus far:
int people = 20;
float prize = 1000;
List<int> list = new List<int>();
for( int i = 0; i < people; ++i )
{
list.add(Random.Range(0,100));
}
list.Sort();
float k = (2 * prize) / ((people) * (people - 1));
float sum = 0;
for (int i = 1; i < list.Count-1; ++i)
{
var personsPrize = i * k;
sum += personsPrize;
Console.WriteLine(personsPrize);
}
Console.WriteLine("sum = " + sum);
First place would get 25% of the total prize pool. Second place gets 20% and third place gets 15% then the rest is divided between the remaining people, with people on the same score getting the same amount.
What should happen for people getting tied first equal? They shouldn't get less than anybody else, but shouldn't double the first prize value.
I'd do it by splitting out the prize fractions first, and determining which prizes should be merged due to ties. Then, sum up the merged fractions and divide that merged amount equally to all the tied participants.
This ensures that the amount received for each tied participant is less than or equal to the greatest prize amount merged in and greater than or equal to the least prize amount merged in.
public class Person
{
public Person(string name, int position)
{
Name = name;
Position = position;
}
public string Name { get; set; }
public int Position { get; set; }
}
static void Main(string[] args)
{
var winners = new Person[]
{
new Person("Test 1", 1),
new Person("Test 2", 1),
new Person("Test 3", 1),
new Person("Test 4", 1),
new Person("Test 5", 5),
new Person("Test 6", 6),
new Person("Test 7", 7),
new Person("Test 8", 8),
new Person("Test 9", 9),
new Person("Test 10", 9),
new Person("Test 11", 11),
new Person("Test 12", 11),
new Person("Test 13", 13),
new Person("Test 14", 14),
new Person("Test 15", 15),
new Person("Test 16", 16),
new Person("Test 17", 17),
new Person("Test 18", 18),
new Person("Test 19", 19),
new Person("Test 20", 19)
};
var prizes = SplitPrizeFund(1000, winners.Length);
AllocatePrizes(winners, prizes);
}
private static void AllocatePrizes(IEnumerable<Person> positions, double[] prizes)
{
var orderedPositions = positions.OrderBy(f => f.Position).ToArray();
for (var pos = 0; pos < orderedPositions.Length;)
{
var currentPerson = orderedPositions[pos];
// Find equally placed people (if any)
var comList = orderedPositions.Skip(pos).Where(f => f.Position == currentPerson.Position).ToList();
// We should now have one or more people in our list
var splitWays = comList.Count;
// Total the prize fund over the places found
double splitFund = prizes.Skip(pos).Take(splitWays).Sum();
// Allocate the total winnings equally between winners of this place
bool first = true;
foreach (var person in comList)
{
if (first)
{
Console.WriteLine($"{person.Name,-20} {(splitFund / splitWays),10:C2}");
first = false;
}
else
{
// Identify equal placed winners
Console.WriteLine($"{person.Name,-19}= {(splitFund / splitWays),10:C2}");
}
}
pos += splitWays;
}
}
private static double[] SplitPrizeFund(double totalFund, int numberOfPrizes)
{
var prizes = new double[numberOfPrizes];
var remainingFund = totalFund;
int remainingPrizes = numberOfPrizes;
// Special handling for top three places
int pos = 0;
prizes[pos] = Math.Round(remainingFund * 0.25, 2, MidpointRounding.AwayFromZero);
pos += 1;
prizes[pos] = Math.Round(remainingFund * 0.20, 2, MidpointRounding.AwayFromZero);
pos += 1;
prizes[pos] = Math.Round(remainingFund * 0.15, 2, MidpointRounding.AwayFromZero);
pos += 1;
remainingPrizes -= 3;
remainingFund -= prizes[0] + prizes[1] + prizes[2];
// Linear reducing split from 4th (replace this with whatever you want)
int totalPortions = 0;
for (int i = 1; i <= remainingPrizes; i++)
totalPortions += i;
for (int i = remainingPrizes; i >= 1; i--)
{
prizes[pos] = Math.Round(remainingFund * i / totalPortions, 2, MidpointRounding.AwayFromZero);
remainingFund -= prizes[pos];
totalPortions -= i;
pos++;
}
return prizes;
}
This can be a solution :
class People
{
public int ID { get; set; }
public int Rank { get; set; }
public float? Prize { get; set; }
}
//Reserves 60% of the prize for the first, second and the third person.
//Imageine that there are 5 people have the highest rank 20 for exemple.
in this case the total of the first place makes 125%. It can't be possible.
For me, you should have another parameter to choose the first places.
Or, you should modify your rank logic: like if the total of percentages is over 100% or 90%, (or a percentage that you will decide), reduce the percentage of the first place, second and third places etc.
Imagene that you have 4 first place, 1 second place and 1 thirt place.
In this case you have (4 * 25%) + 20% + 15% = 135%. It means you have to reduce your 25% to for exemple 15 %, second place to 10% and the third place to 5%.
in this case you will have (4 * 15%) + 10% + 5% = 75 percent for your highest places and you will distribute 25% to other users.
private void CheckPrices()
{
float prize = 1000;
Random rnd = new Random(1);
var peopleList = new List<People>();
for (int i = 0; i < 20; i++)
{
peopleList.Add(new Test.People() { ID = i + 1, Rank = rnd.Next(5, 100) });
}
var firstPrize = prize * 25 / 100;
var secondPrize = prize * 20 / 100;
var thirstPrize = prize * 15 / 100;
int i = 0;
//Sets first places prizes.
foreach (var person in peopleList.OrderByDescending(ro => ro.Rank))
{
i++;
if (i == 1)
person.Prize = firstPrize;
else if (i == 2)
person.Prize = secondPrize;
else if (i == 3)
person.Prize = thirstPrize;
else
break;
}
var totalRank = peopleList.Sum(ro => ro.Rank);
float prizePerRank = (prize - (firstPrize + secondPrize + thirstPrize)) / totalRank;
foreach (var person in peopleList.Where( ro=> ro.Prize == null))
{
person.Prize = person.Rank * prizePerRank;
}
//
var totalPrizeDistributed = peopleList.Sum(ro => ro.Prize); //= 1000
}
}

How to distribute items evenly, without random numbers

I have a situation where I need to evenly distribute N items across M slots. Each item has its own distribution %. For discussion purposes say there are three items (a,b,c) with respective percentages of (50,25,25) to be distributed evenly across 20 slots. Hence 10 X a,5 X b & 5 X c need to be distributed. The outcome would be as follows:
1. a
2. a
3. c
4. b
5. a
6. a
7. c
8. b
9. a
10. a
11. c
12. b
13. a
14. a
15. c
16. b
17. a
18. a
19. c
20. b
The part that I am struggling with is that the number of slots, number of items and percentages can all vary, of course the percentage would always total up to 100%. The code that I wrote resulted in following output, which is always back weighted in favour of item with highest percentage. Any ideas would be great.
1. a
2. b
3. c
4. a
5. b
6. c
7. a
8. b
9. c
10. a
11. c
12. b
13. a
14. b
15. c
16. a
17. a
18. a
19. a
20. a
Edit
This is what my code currently looks like. Results in back weighted distribution as I mentioned earlier. For a little context, I am trying to evenly assign commercials across programs. Hence every run with same inputs has to result in exactly the same output. This is what rules out the use of random numbers.
foreach (ListRecord spl in lstRecords){
string key = spl.AdvertiserName + spl.ContractNumber + spl.AgencyAssignmentCode;
if (!dictCodesheets.ContainsKey(key)){
int maxAssignmentForCurrentContract = weeklyList.Count(c => (c.AdvertiserName == spl.AdvertiserName) && (c.AgencyAssignmentCode == spl.AgencyAssignmentCode)
&& (c.ContractNumber == spl.ContractNumber) && (c.WeekOf == spl.WeekOf));
int tmpAssignmentCount = 0;
for (int i = 0; i < tmpLstGridData.Count; i++)
{
GridData gData = tmpLstGridData[i];
RotationCalculation commIDRotationCalc = new RotationCalculation();
commIDRotationCalc.commercialID = gData.commercialID;
commIDRotationCalc.maxAllowed = (int)Math.Round(((double)(maxAssignmentForCurrentContract * gData.rotationPercentage) / 100), MidpointRounding.AwayFromZero);
tmpAssignmentCount += commIDRotationCalc.maxAllowed;
if (tmpAssignmentCount > maxAssignmentForCurrentContract)
{
commIDRotationCalc.maxAllowed -= 1;
}
if (i == 0)
{
commIDRotationCalc.maxAllowed -= 1;
gridData = gData;
}
commIDRotationCalc.frequency = (int)Math.Round((double)(100/gData.rotationPercentage));
if (i == 1)
{
commIDRotationCalc.isNextToBeAssigned = true;
}
lstCommIDRotCalc.Add(commIDRotationCalc);
}
dictCodesheets.Add(key, lstCommIDRotCalc);
}else{
List<RotationCalculation> lstRotCalc = dictCodesheets[key];
for (int i = 0; i < lstRotCalc.Count; i++)
{
if (lstRotCalc[i].isNextToBeAssigned)
{
gridData = tmpLstGridData.Where(c => c.commercialID == lstRotCalc[i].commercialID).FirstOrDefault();
lstRotCalc[i].maxAllowed -= 1;
if (lstRotCalc.Count != 1)
{
if (i == lstRotCalc.Count - 1 && lstRotCalc[0].maxAllowed > 0)
{
//Debug.Print("In IF");
lstRotCalc[0].isNextToBeAssigned = true;
lstRotCalc[i].isNextToBeAssigned = false;
if (lstRotCalc[i].maxAllowed == 0)
{
lstRotCalc.RemoveAt(i);
}
break;
}
else
{
if (lstRotCalc[i + 1].maxAllowed > 0)
{
//Debug.Print("In ELSE");
lstRotCalc[i + 1].isNextToBeAssigned = true;
lstRotCalc[i].isNextToBeAssigned = false;
if (lstRotCalc[i].maxAllowed == 0)
{
lstRotCalc.RemoveAt(i);
}
break;
}
}
}
}
}
}
}
Edit 2
Trying to clear up my requirement here. Currently, because item 'a' is to be assigned 10 times which is the highest among all three items, towards the end of distribution, items 16 - 20 all have been assigned only 'a'. As has been asked in comments, I am trying to achieve a distribution that "looks" more even.
One way to look at this problem is as a multi-dimensional line drawing problem. So I used Bresenham's line algorithm to create the distribution:
public static IEnumerable<T> GetDistribution<T>( IEnumerable<Tuple<T, int>> itemCounts )
{
var groupCounts = itemCounts.GroupBy( pair => pair.Item1 )
.Select( g => new { Item = g.Key, Count = g.Sum( pair => pair.Item2 ) } )
.OrderByDescending( g => g.Count )
.ToList();
int maxCount = groupCounts[0].Count;
var errorValues = new int[groupCounts.Count];
for( int i = 1; i < errorValues.Length; ++i )
{
var item = groupCounts[i];
errorValues[i] = 2 * groupCounts[i].Count - maxCount;
}
for( int i = 0; i < maxCount; ++i )
{
yield return groupCounts[0].Item;
for( int j = 1; j < errorValues.Length; ++j )
{
if( errorValues[j] > 0 )
{
yield return groupCounts[j].Item;
errorValues[j] -= 2 * maxCount;
}
errorValues[j] += 2 * groupCounts[j].Count;
}
}
}
The input is the actual number of each item you want. This has a couple advantages. First it can use integer arithmetic, which avoids any rounding issues. Also it gets rid of any ambiguity if you ask for 10 items and want 3 items evenly distributed (which is basically just the rounding issue again).
Here's one with no random number that gives the required output.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
// name, percentage
Dictionary<string, double> distribution = new Dictionary<string,double>();
// name, amount if one more were to be distributed
Dictionary<string, int> dishedOut = new Dictionary<string, int>();
//Initialize
int numToGive = 20;
distribution.Add("a", 0.50);
distribution.Add("b", 0.25);
distribution.Add("c", 0.25);
foreach (string name in distribution.Keys)
dishedOut.Add(name, 1);
for (int i = 0; i < numToGive; i++)
{
//find the type with the lowest weighted distribution
string nextUp = null;
double lowestRatio = double.MaxValue;
foreach (string name in distribution.Keys)
if (dishedOut[name] / distribution[name] < lowestRatio)
{
lowestRatio = dishedOut[name] / distribution[name];
nextUp = name;
}
//distribute it
dishedOut[nextUp] += 1;
Console.WriteLine(nextUp);
}
Console.ReadLine();
}
}
Instead of a truly random number generator, use a fixed seed, so that the program has the same output every time you run it (for the same input). In the code below, the '0' is the seed, which means the 'random' numbers generated will always be the same each time the program is run.
Random r = new Random(0);
//AABC AABC…
int totalA = 10
int totalB = 5
int totalC = 5
int totalItems = 20 //A+B+C
double frequencyA = totalA / totalItems; //0.5
double frequencyB = totalB / totalItems; //0.25
double frequencyC = totalC / totalItems; //0.25
double filledA = frequencyA;
double filledB = frequencyB;
double filledC = frequencyC;
string output = String.Empty;
while(output.Length < totalItems)
{
filledA += frequencyA;
filledB += frequencyB;
filledC += frequencyC;
if(filledA >= 1)
{
filledA -= 1;
output += "A";
if(output.Length == totalItems){break;}
}
if(filledB >= 1)
{
filledB -= 1
output += "B";
if(output.Length == totalItems){break;}
}
if(filledC >= 1)
{
filledC -= 1
output += "C";
if(output.Length == totalItems){break;}
}
}
This answer was mostly stolen and lightly adapted for your use from here
My idea is that you distribute your items in the simplest way possible without care of order, then shuffle the list.
public static void ShuffleTheSameWay<T>(this IList<T> list)
{
Random rng = new Random(0);
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
Fiddle here

Max average series in ordered list

I'm trying to get the greatest average values for different duration in a list.
Let's say I have the following data:
var randomList = new List<int>();
var random = new Random(1969);
for (var i = 0; i < 10; i++)
{
randomList.Add(random.Next(0, 500));
}
That produces the following list:
190
279
37
413
90
131
64
129
287
172
I'm trying to get the highest average values for the different sets 0-9.
Set 0 (one item in a row) = 413 (index 3)
Set 1 (two items in a row) = 252 (average index 3,4)
Set 9 (10 items in a row) = 179 (average of the entire list)
I've been beating my head on this a while. I'm trying to find an efficient way to write this so I have the least traversals as possible. In production, I'll have lists with 3500-6000 points.
How do I find the highest average values for the different sets 0-9?
This probably isn't the most efficient way to do it, but it works fine:
Basically, we use a stack to track the items we've traversed. Then to calculate the average for n last items, we peek at n items from the stack.
void Main()
{
var randomList = new List<int>();
var random = new Random(1969);
for (var i = 0; i < 10; i++)
{
randomList.Add(random.Next(0, 500));
}
// Use the values from the original post for validation
randomList = new List<int> { 190, 279, 37, 413, 90, 131, 64, 129, 287, 172 };
const int numSets = 9;
var avgDict = Enumerable.Range(1, numSets).ToDictionary(e => e, e => (double)0);
var s = new Stack<int>();
foreach (var item in randomList)
{
s.Push(item);
for (var i = 1; i <= numSets; i++)
{
if (s.Count >= i)
{
var avg = s.Take(i).Average();
if (avg > avgDict[i])
avgDict[i] = avg;
}
}
}
avgDict.Dump();
}
Yields the result:
1 413
2 251.5
3 243
4 229.75
5 201.8
6 190
7 183.714285714286
8 178.75
9 180
I'm unsure as to the implications of using a Stack for large lists, when we only need 9-10 items. Might be a good case for a custom limited size stack
In your comment, you mentioned Avg(items:0,1,2) vs Avg(items:1,2,3) vs Avg(items:2,3,4)
Not sure if this is what you want but I came up with this.
First, get random number, then get average of 3 numbers. Then, get the largest average value.
static void Main(string[] args)
{
var randomList = new List<int>();
var random = new Random(1969);
int TotalRandomNumber = 10; //Change this accordingly
for (var i = 0; i < TotalRandomNumber ; i++)
{
randomList.Add(random.Next(0, 500));
}
foreach (var item in randomList)
{
Console.WriteLine("Random Number: " + item);
}
var AveNum = new List<double>();
int range = 3; //Change this for different range
for (int i = 1; i < TotalRandomNumber - range; i++)
{
var three = randomList.GetRange(i, range);
double result = three.Average();
Console.WriteLine("Average Number: " + result);
AveNum.Add(result);
}
Console.WriteLine("Largest: " + AveNum.Max());
}

List or dictionary in C#

I need two lists as output, one of which is a list of index and the other is of the corresponding value till the condition is satisfied..
//initializing the first value of TotalDebts
double TotalDebts = 30000;
for (int i = 0; i < 250; i++)
{
if (TotalDebts > 0)
{
double DebtsLessIncome = Convert.ToDouble(TotalDebts - 1000);
double InterestCharged = Convert.ToDouble((DebtsLessIncome * 5) / 100);
double InterestDebt = Convert.ToDouble(DebtsLessIncome + InterestCharged);
double InterestDebtMLE = Convert.ToDouble(InterestDebt + 500);
double TotalDebts = Convert.ToDouble(InterestDebtMLE);
//how to add TotalDebts in list or dictionary from each loop as index 0,1,2 and so on
List<double> AllDebits = new List<double>();
AllDebits.Add(TotalDebts);
// how to retrieve each index and value and store index in one list and value in second list
}
}
Based on "how to retrieve each index and value" - I assume you want to access the data by index (=year?) - dictionary works fine for that.
double TotalDebts = 30000;
Dictionary<int, double> dResult = new Dictionary<int, double>();
for (int i = 0; i < 250; i++)
{
if (TotalDebts > 0)
{
double DebtsLessIncome = Convert.ToDouble(TotalDebts - 1000);
double InterestCharged = Convert.ToDouble((DebtsLessIncome * 5) / 100);
double InterestDebt = Convert.ToDouble(DebtsLessIncome + InterestCharged);
double InterestDebtMLE = Convert.ToDouble(InterestDebt + 500);
TotalDebts = Convert.ToDouble(InterestDebtMLE);
dResult.Add(i, TotalDebts);
}
}
Notes
Dictionary does not support ordered iteration so if you need to output list of year/debt pairs use some other data structure (i.e. List<KeyValuePair<int, decimal>>).
usually one would use decimal for money values instead of float/double.
Update (split dictionary into 2 lists)
List<int> lIndex = dResult.Select(x => x.Key).ToList();
List<double> lDepts = dResult.Select(x => x.Value).ToList();
I would use a list of tuples like this:
double TotalDebts = 30000;
list<Tuple<int, double>> AllDebits = new list<Tuple<int, double>>();
for (int i = 0; i < 250; i++)
{
if (TotalDebts > 0)
{
double DebtsLessIncome = Convert.ToDouble(TotalDebts - 1000);
double InterestCharged = Convert.ToDouble((DebtsLessIncome * 5) / 100);
double InterestDebt = Convert.ToDouble(DebtsLessIncome + InterestCharged);
double InterestDebtMLE = Convert.ToDouble(InterestDebt + 500);
TotalDebts = Convert.ToDouble(InterestDebtMLE);
AllDebits.Add(new Tuple<int,double>(i, TotalDebts));
}
}
It really depends on what you'll be using this for. If the data is really just to display in a list then this is fine. However, if the index is to be used for something else (id perhaps?) then a Dictionary (as per fubo's answer) would be much better, as the index has meaning and needs to be unique (Dictionaries don't allow duplicate keys).

Categories