I have a CSV formatted league schedule as input:
1,2 @ 1,7 @ 4,5 @ 6,3 @ 8
2,4 @ 2,1 @ 3,8 @ 5,6 @ 7
3,4 @ 1,5 @ 3,2 @ 6,7 @ 8
4,8 @ 2,6 @ 4,1 @ 5,3 @ 7
5,6 @ 1,2 @ 3,7 @ 5,4 @ 8
6,5 @ 2,3 @ 4,8 @ 6,1 @ 7
7,8 @ 1,6 @ 3,4 @ 5,2 @ 7
8,1 @ 2,8 @ 3,6 @ 5,4 @ 7
9,3 @ 1,2 @ 4,7 @ 6,5 @ 8
10,6 @ 2,1 @ 4,3 @ 5,8 @ 7
11,5 @ 1,7 @ 3,4 @ 6,2 @ 8
12,3 @ 2,8 @ 4,1 @ 6,5 @ 7
13,7 @ 1,4 @ 3,2 @ 5,6 @ 8
14,7 @ 2,5 @ 4,3 @ 6,1 @ 8
15,2 @ 1,7 @ 4,5 @ 6,3 @ 8
16,4 @ 2,1 @ 3,8 @ 5,6 @ 7
The schedule is made up of 8 teams where each team will be matched up against another team weekly (4 match ups) over the course of 16 weeks.
- Column 1: Week number
- Column 2: Match 1
- Column 3: Match 2
- etc..
I'm trying to divide the match ups evenly across "X" locations where "X" equals the number of matches a week.
The initial algorithm I came up with was to track how many times a team has a match at a location in order to give the location a weighted value. The algorithm selects the location with the lowest weight value.
schedule.GetAllScheduledRecords().ForEach(sr =>
{
var availableLocations = new List<int>(locationKeys);
sr.GetAllLeagueMatches().ForEach(lm =>
{
var weights = new List<Tuple<int, int>>();
availableLocations.ForEach(location =>
weights.Add(new Tuple<int, int>(locationWeight[lm.GetAwayTeam()][location] + locationWeight[lm.GetHomeTeam()][location], location)));
var lowestWeight = weights.Min(x => x.Item1);
var lowestLocations = weights.Where(x => x.Item1 == lowestWeight).ToList();
var locationIndex = rng.Next(lowestLocations.Count);
var chosenLocation = lowestLocations [tableIndex].Item2;
availableLocations .Remove(chosenLocation);
locationAssignments[chosenLocation ][sr.GetLeagueWeek() - 1] = lm;
locationWeight[lm.GetAwayTeam()][chosenLocation]++;
locationWeight[lm.GetHomeTeam()][chosenLocation]++;
});
});
This works for the most part, but sometimes the result is not as even as it possibly can be. This example result has Team 1 playing a location 1 five times when, in this case, the distribution should be where teams will play at each location 4 times and sometimes it does create that result.
How could I modify this distribution algorithm to where it'll be as even as possible? The algorithm also needs to be scalable in case there are more or less match ups per week and/or more or less weeks in a session. For example, another schedule has 12 teams (6 match ups / week = 6 locations) so the distribution should be some combination of 3, 3, 3, 3, 2, 2.
I have a CSV formatted league schedule as input:
1,2 @ 1,7 @ 4,5 @ 6,3 @ 8
2,4 @ 2,1 @ 3,8 @ 5,6 @ 7
3,4 @ 1,5 @ 3,2 @ 6,7 @ 8
4,8 @ 2,6 @ 4,1 @ 5,3 @ 7
5,6 @ 1,2 @ 3,7 @ 5,4 @ 8
6,5 @ 2,3 @ 4,8 @ 6,1 @ 7
7,8 @ 1,6 @ 3,4 @ 5,2 @ 7
8,1 @ 2,8 @ 3,6 @ 5,4 @ 7
9,3 @ 1,2 @ 4,7 @ 6,5 @ 8
10,6 @ 2,1 @ 4,3 @ 5,8 @ 7
11,5 @ 1,7 @ 3,4 @ 6,2 @ 8
12,3 @ 2,8 @ 4,1 @ 6,5 @ 7
13,7 @ 1,4 @ 3,2 @ 5,6 @ 8
14,7 @ 2,5 @ 4,3 @ 6,1 @ 8
15,2 @ 1,7 @ 4,5 @ 6,3 @ 8
16,4 @ 2,1 @ 3,8 @ 5,6 @ 7
The schedule is made up of 8 teams where each team will be matched up against another team weekly (4 match ups) over the course of 16 weeks.
- Column 1: Week number
- Column 2: Match 1
- Column 3: Match 2
- etc..
I'm trying to divide the match ups evenly across "X" locations where "X" equals the number of matches a week.
The initial algorithm I came up with was to track how many times a team has a match at a location in order to give the location a weighted value. The algorithm selects the location with the lowest weight value.
schedule.GetAllScheduledRecords().ForEach(sr =>
{
var availableLocations = new List<int>(locationKeys);
sr.GetAllLeagueMatches().ForEach(lm =>
{
var weights = new List<Tuple<int, int>>();
availableLocations.ForEach(location =>
weights.Add(new Tuple<int, int>(locationWeight[lm.GetAwayTeam()][location] + locationWeight[lm.GetHomeTeam()][location], location)));
var lowestWeight = weights.Min(x => x.Item1);
var lowestLocations = weights.Where(x => x.Item1 == lowestWeight).ToList();
var locationIndex = rng.Next(lowestLocations.Count);
var chosenLocation = lowestLocations [tableIndex].Item2;
availableLocations .Remove(chosenLocation);
locationAssignments[chosenLocation ][sr.GetLeagueWeek() - 1] = lm;
locationWeight[lm.GetAwayTeam()][chosenLocation]++;
locationWeight[lm.GetHomeTeam()][chosenLocation]++;
});
});
This works for the most part, but sometimes the result is not as even as it possibly can be. This example result has Team 1 playing a location 1 five times when, in this case, the distribution should be where teams will play at each location 4 times and sometimes it does create that result.
How could I modify this distribution algorithm to where it'll be as even as possible? The algorithm also needs to be scalable in case there are more or less match ups per week and/or more or less weeks in a session. For example, another schedule has 12 teams (6 match ups / week = 6 locations) so the distribution should be some combination of 3, 3, 3, 3, 2, 2.
Share Improve this question edited Jan 20 at 6:53 Shar1er80 asked Jan 20 at 6:10 Shar1er80Shar1er80 9,0412 gold badges23 silver badges31 bronze badges 10- Is this for Java or C#? (or did someone create JC# but haven't made the tag?) – ewokx Commented Jan 20 at 6:41
- @ewokx Either really, but since I started in C# I went ahead and removed the java tag – Shar1er80 Commented Jan 20 at 6:44
- What exactly do you mean by "evenly"? Any Real-Life League I know works with "Lefthand side of the match ( "A vs B" ) is the 'Home' Team and therefore hosting the Game ( => A )". Or is "1,2,3,4" the "location" here, unrelated to the team? – Fildor Commented Jan 20 at 8:13
- This specific league is a 9-ball pool league. 1, 2, 3, 4 represents the table where the match would take place. – Shar1er80 Commented Jan 20 at 8:27
- Maybe worthwhile to specify that in the question. Comments are to be considered volatile. – Fildor Commented Jan 20 at 8:34
1 Answer
Reset to default 0I assumed that you get the same data you provide from CSV and put
int numberOfLocations = 4; // Number of available locations per week
int maxMatchesPerLocation = 4; // Maximum matches that the team can play at a location
First I read the CSV file
var schedule = File.ReadAllLines(csvPath)
.Skip(1) // I Skiped the header
.Select(line => line.Split(','))
.Select(parts => new { Week = int.Parse(parts[0]), Matches = parts.Skip(1).ToList() })
.ToList();
and initialized location weeks and the weights
foreach (var week in schedule)
{
locationWeeks[week.Week] = new Dictionary<int, string>();
foreach (var match in week.Matches)
{
var teams = match.Split('@').Select(t => int.Parse(t.Trim())).ToList();
foreach (var team in teams)
{
if (!locationWeight.ContainsKey(team))
locationWeight[team] = new Dictionary<int, int>();
for (int i = 1; i <= numberOfLocations; i++)
if (!locationWeight[team].ContainsKey(i))
locationWeight[team][i] = 0;
}
}
}
I tried Greedy Algorithm to calculate weights for each location per given two teams based on the given data that you provide in your question .
check maximum allowed matches per team .
the lowest weight is chosen location
foreach (var week in schedule){ var availableLocations = Enumerable.Range(1, numberOfLocations).ToList(); foreach (var match in week.Matches) { var teams = match.Split('@').Select(t => int.Parse(t.Trim())).ToList(); // Calculate weights for each location with the maxMatchesPerLocation var weights = availableLocations.Select(location => new { Location = location, Weight = locationWeight[teams[0]][location] + locationWeight[teams[1]][location], ExceedsLimit = locationWeight[teams[0]][location] >= maxMatchesPerLocation || locationWeight[teams[1]][location] >= maxMatchesPerLocation }).Where(w => !w.ExceedsLimit).ToList(); // If no locations are available within limits, fallback to all locations if (!weights.Any()) { weights = availableLocations.Select(location => new { Location = location, Weight = locationWeight[teams[0]][location] + locationWeight[teams[1]][location], ExceedsLimit = false }).ToList(); } var minWeight = weights.Min(w => w.Weight); var chosenLocation = weights.First(w => w.Weight == minWeight).Location; locationWeeks[week.Week][chosenLocation] = match; locationWeight[teams[0]][chosenLocation]++; locationWeight[teams[1]][chosenLocation]++; availableLocations.Remove(chosenLocation); }}
the Big O : Outer loop ----> Means W weeks. Inner loop ----> Means M matches per week. Weight Calculations(foreach calculates weights for all availableLocations ) ----> Means C So it's O(W . M . C)