Last active
July 30, 2019 18:20
-
-
Save maydaytx/53c24cb86d82b226c00e to your computer and use it in GitHub Desktop.
Randomly select bowlers to exclude each week
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
const int weeks = 12; | |
const int bowlersPerWeek = 4; | |
var bowlers = new[] | |
{ | |
new Bowler("Bob", 5, 8), | |
new Bowler("Ben", 2), | |
new Bowler("Janna", 5), | |
new Bowler("Jonny", 0, 8), | |
new Bowler("Jeremy", 0, 3), | |
new Bowler("Patrick", 3) | |
}; | |
var maximumNumberOfWeeksPerBowler = (int) Math.Ceiling(1m * weeks * bowlersPerWeek / bowlers.Length); | |
do | |
{ | |
GenerateSchedule(weeks, bowlersPerWeek, maximumNumberOfWeeksPerBowler, bowlers); | |
} while (bowlers.Any(x => x.Schedule.Count(y => y.Value) > maximumNumberOfWeeksPerBowler)); | |
bowlers.Select(x => x.Schedule.Count(y => y.Value)).ToArray().Dump(); | |
var schedule = new string[weeks, bowlers.Length]; | |
for (var i = 0; i < weeks; ++i) | |
for (var j = 0; j < bowlers.Length; ++j) | |
schedule[i, j] = bowlers[j].Schedule.Get(i) == true ? "x" : ""; | |
schedule.Dump(); | |
} | |
private static void GenerateSchedule(int weeks, int bowlersPerWeek, int maximumNumberOfWeeksPerBowler, IReadOnlyCollection<Bowler> bowlers) | |
{ | |
foreach (var bowler in bowlers) | |
bowler.Schedule.Clear(); | |
var bowlersToExcludePerWeek = bowlers.Count - bowlersPerWeek; | |
var random = new Random(); | |
for (var week = 0; week < weeks; ++week) | |
{ | |
var bowlerWeights = bowlers | |
.Select(x => new {Bowler = x, ExclusionWeight = x.GetExclusionWeight(week, maximumNumberOfWeeksPerBowler)}) | |
.ToList(); | |
for (var count = 0; count < bowlersToExcludePerWeek; ++count) | |
{ | |
int index; | |
if (bowlerWeights.Count == 1) | |
{ | |
index = 0; | |
} | |
else | |
{ | |
var totalWeight = bowlerWeights.Sum(x => x.ExclusionWeight); | |
var runningWeight = 0d; | |
var cumulativeWeights = bowlerWeights | |
.Select(x => runningWeight += (x.ExclusionWeight / totalWeight)) | |
.ToList(); | |
index = cumulativeWeights.BinarySearch(random.NextDouble()); | |
if (index < 0) | |
index = (index * -1) - 1; | |
} | |
bowlerWeights[index].Bowler.Schedule.Add(week, false); | |
bowlerWeights.RemoveAt(index); | |
} | |
foreach (var bowler in bowlerWeights.Select(x => x.Bowler)) | |
bowler.Schedule.Add(week, true); | |
} | |
} | |
private class Bowler | |
{ | |
public string Name { get; } | |
public CoolDictionary<int, bool> Schedule { get; } = new CoolDictionary<int, bool>(); | |
private readonly int[] _excludedWeeks; | |
public Bowler(string name, params int[] excludedWeeks) | |
{ | |
Name = name; | |
_excludedWeeks = excludedWeeks; | |
} | |
public double GetExclusionWeight(int week, int maximumNumberOfWeeksPerBowler) | |
{ | |
var bowlerWeeks = Schedule.Count(x => x.Value); | |
if (_excludedWeeks.Contains(week)) | |
return 100000; | |
if (bowlerWeeks == maximumNumberOfWeeksPerBowler) | |
return 500; | |
if (Schedule.Get(week - 1) == false) | |
{ | |
if (Schedule.Get(week - 2) == true) | |
return maximumNumberOfWeeksPerBowler * 2; //prioritize two weeks off in a row | |
if (Schedule.Get(week - 3) == false) | |
return 1 + bowlerWeeks; //de-prioritize >2 weeks off in a row | |
} | |
return 1 + bowlerWeeks * 2; | |
} | |
} | |
private class CoolDictionary<T, V> : Dictionary<T, V> where V : struct | |
{ | |
public V? Get(T key) | |
{ | |
if (ContainsKey(key)) | |
return this[key]; | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment