Add Team functions
This commit is contained in:
@@ -57,7 +57,6 @@ namespace Core.Calculation
|
||||
_twoTeams = events;
|
||||
}
|
||||
|
||||
|
||||
public async Task<EventAssignmentSolution> Solve()
|
||||
{
|
||||
Debug.WriteLine(_parameters);
|
||||
@@ -78,21 +77,25 @@ namespace Core.Calculation
|
||||
LimitStudentAssignment(model, x);
|
||||
|
||||
// set the range for level of effort
|
||||
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
|
||||
SetLevelOfEffort(model, x, eventEffortCoefficients);
|
||||
SetLevelOfEffort(model, x);
|
||||
|
||||
// each student should be assigned at least one on site activity eventDefinition
|
||||
// each student should be assigned at least one on site activity
|
||||
if (_parameters.RequireOnSite)
|
||||
RequireOnSiteActivity(model, x);
|
||||
|
||||
// students should have at maximum one individual eventDefinition
|
||||
LimitIndividualEvent(model, x);
|
||||
|
||||
|
||||
// students should have at least one regional event
|
||||
if (_parameters.RequireRegional)
|
||||
RequireRegionalEvent(model, x);
|
||||
|
||||
OptimizeStudentEventRankings(model, x);
|
||||
// students should have at maximum one individual event
|
||||
AtMostOneIndividualEvent(model, x);
|
||||
|
||||
//EventHasInterestedStudent(model, x);
|
||||
|
||||
IndividualEventsMustBeRanked(model, x);
|
||||
|
||||
OptimizeStudentEventRankings(model, x);
|
||||
|
||||
|
||||
Debug.WriteLine("Starting optimization");
|
||||
var solver = new CpSolver();
|
||||
@@ -114,36 +117,34 @@ namespace Core.Calculation
|
||||
return eventAssignmentSolution;
|
||||
}
|
||||
|
||||
// Take the solution and map it back to the entities
|
||||
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
|
||||
{
|
||||
var eventAssignmentsList = new List<Team>();
|
||||
if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible))
|
||||
return [];
|
||||
|
||||
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
|
||||
{
|
||||
foreach (var e in _allEvents)
|
||||
var eventAssignments =
|
||||
from e in _allEvents
|
||||
let students =
|
||||
from s in _allStudents
|
||||
where solver.BooleanValue(x[e, s])
|
||||
select _students[s]
|
||||
where students.Any()
|
||||
select new Team
|
||||
{
|
||||
var students = new List<Student>();
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
if (solver.BooleanValue(x[e, s]))
|
||||
{
|
||||
students.Add(_students[s]);
|
||||
|
||||
}
|
||||
}
|
||||
if (students.Count > 0)
|
||||
eventAssignmentsList.Add(new Team { TeamId = _events[e].Name, Event = _events[e], Students = students});
|
||||
}
|
||||
}
|
||||
TeamId = _events[e].Name,
|
||||
Event = _events[e],
|
||||
Students = students.ToList()
|
||||
};
|
||||
|
||||
return eventAssignmentsList;
|
||||
return eventAssignments.ToList();
|
||||
}
|
||||
|
||||
// Maximize student event rankings
|
||||
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
var maximizePicks = LinearExpr.NewBuilder();
|
||||
|
||||
// optimize student event rankings
|
||||
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
|
||||
@@ -167,13 +168,13 @@ namespace Core.Calculation
|
||||
regionalEvent.Add(x[e, s]);
|
||||
}
|
||||
|
||||
//if (_parameters.RequireRegional)
|
||||
// between 1 and 2 regional events
|
||||
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
|
||||
regionalEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void LimitIndividualEvent(CpModel model, BoolVar[,] x)
|
||||
private void AtMostOneIndividualEvent(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
@@ -206,8 +207,30 @@ namespace Core.Calculation
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLevelOfEffort(CpModel model, BoolVar[,] x, long[] eventEffortCoefficients)
|
||||
private void IndividualEventsMustBeRanked(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var student = _students[s];
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var evt = _events[e];
|
||||
|
||||
var prohibitVar = new List<IntVar> { x[e, s] };
|
||||
if (evt.EventFormat == EventFormat.Individual
|
||||
&& student.EventRankings.Find(er => er.EventDefinition == evt) == null)
|
||||
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
|
||||
prohibitVar.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLevelOfEffort(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
long[] eventEffortCoefficients = _events.Select(
|
||||
e => e.LevelOfEffort ?? 1L
|
||||
).ToArray();
|
||||
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var effortVar = new BoolVar[_allEvents.Length];
|
||||
@@ -234,9 +257,9 @@ namespace Core.Calculation
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of events a student is assigned
|
||||
private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
// Limit the number of events a student is assigned
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var studentCapacity = new List<IntVar>();
|
||||
@@ -251,6 +274,25 @@ namespace Core.Calculation
|
||||
}
|
||||
}
|
||||
|
||||
private void EventHasInterestedStudent(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var evt = _events[e];
|
||||
var studentInterest = new List<IntVar>();
|
||||
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var student = _students[s];
|
||||
if (student.EventRankings.Find(er => er.EventDefinition == evt) != null)
|
||||
studentInterest.Add(x[e, s]);
|
||||
}
|
||||
model.AddLinearConstraint(
|
||||
LinearExpr.Sum(studentInterest), 1, 10);
|
||||
studentInterest.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
var assignmentThresholdsList = new List<EventAssignmentThresholds>();
|
||||
@@ -280,6 +322,9 @@ namespace Core.Calculation
|
||||
)
|
||||
teamCount = 1;
|
||||
|
||||
if (_twoTeams.Contains(evt))
|
||||
teamCount = 2;
|
||||
|
||||
if (evt.Name == "Tech Bowl")
|
||||
teamCount = 1;
|
||||
|
||||
@@ -292,6 +337,9 @@ namespace Core.Calculation
|
||||
if (_droppedEvents != null && _droppedEvents.Contains(evt))
|
||||
teamCount = 0;
|
||||
|
||||
if (evt.EventFormat == EventFormat.Individual)
|
||||
evtMinTeamSize = 0;
|
||||
|
||||
var lb = evtMinTeamSize * teamCount;
|
||||
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
|
||||
|
||||
@@ -347,22 +395,11 @@ namespace Core.Calculation
|
||||
return
|
||||
eventRank == null
|
||||
? 0L
|
||||
// TODO: MaxRank can be calculated
|
||||
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private long[] GetEventEffortCoefficients(IEnumerable<EventDefinition> events)
|
||||
{
|
||||
return
|
||||
events.Select(
|
||||
e =>
|
||||
//e.Name == "Tech Bowl"
|
||||
// ? 0 // Tech bowl gets requires no effort?
|
||||
//:
|
||||
e.LevelOfEffort ?? 10L
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
public class SolutionPrinter : CpSolverSolutionCallback
|
||||
{
|
||||
private readonly IList<EventDefinition> _events;
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
using Core.Entities;
|
||||
using System.Diagnostics;
|
||||
using Core.Entities;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
namespace Core.Calculation;
|
||||
public class TeamScheduler
|
||||
{
|
||||
private readonly IList<Student> _studentObjs;
|
||||
private readonly IList<Team> _teamObjs;
|
||||
private readonly IList<Student> _studentObjects;
|
||||
private readonly IList<Team> _teamObjects;
|
||||
|
||||
private readonly int[] _students;
|
||||
private readonly int[] _teams;
|
||||
private readonly int[] _timeSlots;
|
||||
|
||||
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = new ();
|
||||
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = [];
|
||||
|
||||
public TeamScheduler(IList<Team> teams, int numTimeSlots)
|
||||
public TeamScheduler(Team[] teams, int numTimeSlots)
|
||||
{
|
||||
_teamObjs = teams;
|
||||
_studentObjs = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||
_teamObjects = teams;
|
||||
_studentObjects = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||
|
||||
_students = Enumerable.Range(0, _studentObjs.Count).ToArray();
|
||||
_teams = Enumerable.Range(0, _teamObjs.Count).ToArray();
|
||||
_students = Enumerable.Range(0, _studentObjects.Count).ToArray();
|
||||
_teams = Enumerable.Range(0, _teamObjects.Count).ToArray();
|
||||
_timeSlots = Enumerable.Range(0, numTimeSlots).ToArray();
|
||||
}
|
||||
|
||||
public void ScheduleSeparate(Team team1, Team team2)
|
||||
{
|
||||
var one = _teamObjs.IndexOf(team1);
|
||||
var two = _teamObjs.IndexOf(team2);
|
||||
var one = _teamObjects.IndexOf(team1);
|
||||
var two = _teamObjects.IndexOf(team2);
|
||||
_scheduleSeparateTeams.Add(Tuple.Create(one,two));
|
||||
}
|
||||
|
||||
public static TeamScheduler CreateInstance(IList<Team> teams, int numTimeSlots)
|
||||
public static TeamScheduler CreateInstance(Team[] teams, int numTimeSlots)
|
||||
{
|
||||
return new TeamScheduler(teams, numTimeSlots);
|
||||
}
|
||||
|
||||
public IList<Team>[] Solve()
|
||||
public TeamSchedulerSolution Solve()
|
||||
{
|
||||
// Model.
|
||||
var model = new CpModel();
|
||||
@@ -44,7 +45,7 @@ public class TeamScheduler
|
||||
var m = new int[_students.Length,_teams.Length];
|
||||
foreach (var i in _students)
|
||||
foreach (var t in _teams)
|
||||
m[i, t] = _studentObjs[i].Teams.Contains(_teamObjs[t]) ? 1 : 0;
|
||||
m[i, t] = _studentObjects[i].Teams.Contains(_teamObjects[t]) ? 1 : 0;
|
||||
|
||||
// Variables.
|
||||
// x - 1 if meeting of team t takes place at time slot s, else 0
|
||||
@@ -82,55 +83,24 @@ public class TeamScheduler
|
||||
foreach (var ts in _scheduleSeparateTeams)
|
||||
foreach (var s in _timeSlots)
|
||||
model.Add(x[ts.Item1, s] != x[ts.Item2, s]);
|
||||
//model.Add(
|
||||
// new BoundedLinearExpression(
|
||||
// x[ts.Item1, s],
|
||||
// x[ts.Item2, s],
|
||||
// false));
|
||||
|
||||
var solver = new CpSolver();
|
||||
|
||||
var cpSolverStatus = solver.Solve(model);
|
||||
Console.WriteLine($"Solver status: {cpSolverStatus}");
|
||||
Debug.WriteLine($"Solver status: {cpSolverStatus}");
|
||||
var timeSlotTeams = new Team[_timeSlots.Length][];
|
||||
|
||||
if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible)
|
||||
{
|
||||
Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
|
||||
if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible))
|
||||
return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString());
|
||||
|
||||
//foreach (var t in _teams)
|
||||
//foreach (var s in _timeSlots)
|
||||
//{
|
||||
// if (solver.Value(x[t, s]) > 0)
|
||||
// Console.WriteLine($"{_teamObjs[t].Name} : {s}");
|
||||
//}
|
||||
|
||||
//foreach (var i in _students)
|
||||
//foreach (var s in _timeSlots)
|
||||
//{
|
||||
// if (solver.Value(y[i, s]) > 0)
|
||||
// Console.WriteLine($"{_studentObjs[i].Name} : {s}");
|
||||
//}
|
||||
|
||||
var timeSlotTeams = new List<Team>[_timeSlots.Length];
|
||||
foreach (var s in _timeSlots)
|
||||
{
|
||||
var teams = new List<Team>();
|
||||
foreach (var t in _teams)
|
||||
{
|
||||
if (solver.Value(x[t, s]) > 0)
|
||||
{
|
||||
teams.Add(_teamObjs[t]);
|
||||
}
|
||||
}
|
||||
timeSlotTeams[s] = teams;
|
||||
}
|
||||
return timeSlotTeams;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine("No solution found.");
|
||||
return Array.Empty<IList<Team>>();
|
||||
}
|
||||
Debug.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
|
||||
|
||||
foreach (var s in _timeSlots)
|
||||
{
|
||||
var teams = (from t in _teams where solver.Value(x[t, s]) > 0 select _teamObjects[t]).ToArray();
|
||||
timeSlotTeams[s] = teams;
|
||||
}
|
||||
//Debug.WriteLine("No solution found.");
|
||||
return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Core.Calculation;
|
||||
|
||||
public class TeamSchedulerOptions(
|
||||
int timeSlots = 3,
|
||||
string[]? absentStudents = null,
|
||||
string[]? extended = null,
|
||||
string[]? omittedEvents = null,
|
||||
string[]? mustIncludeEvents = null,
|
||||
DateTime date = new()
|
||||
)
|
||||
{
|
||||
public int TimeSlots = timeSlots;
|
||||
public string[]? AbsentStudents = absentStudents;
|
||||
public string[]? ExtendedTeams = extended;
|
||||
public string[]? OmittedEvents = omittedEvents;
|
||||
public string[]? MustIncludeEvents = mustIncludeEvents;
|
||||
public DateTime Date { get; } = date == new DateTime() ? DateTime.Today : date;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Calculation;
|
||||
|
||||
public class TeamSchedulerSolution(
|
||||
Team[][] timeSlots,
|
||||
string status)
|
||||
{
|
||||
public Team[][] TimeSlots { get; set; } = timeSlots;
|
||||
public string Status { get; set; } = status;
|
||||
|
||||
|
||||
public static int GetStudentTeamOverlapCount(Team[][] timeSlots)
|
||||
{
|
||||
return timeSlots.Sum(GetStudentTeamOverlapCount);
|
||||
}
|
||||
|
||||
private static int GetStudentTeamOverlapCount(Team[] timeSlot)
|
||||
{
|
||||
return GetStudentTeamOverlaps(timeSlot).Count();
|
||||
}
|
||||
|
||||
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(Team[] timeSlot)
|
||||
{
|
||||
return
|
||||
from s in timeSlot.SelectMany(ts => ts.Students).Distinct()
|
||||
group s by timeSlot.Where(t => t.Students.Contains(s))
|
||||
into gs
|
||||
where gs.Key.Count() > 1
|
||||
select Tuple.Create(gs.First(), gs.Key);
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,18 @@
|
||||
namespace Core.Calculation;
|
||||
public class TeamScheduler_DecisionTree
|
||||
{
|
||||
private readonly IList<Student> _students;
|
||||
private readonly IList<Team> _teams;
|
||||
private readonly Team[] _teams;
|
||||
|
||||
private readonly int _timeSlotCount;
|
||||
|
||||
public TeamScheduler_DecisionTree(IList<Team> teams, int timeSlotCount)
|
||||
public TeamScheduler_DecisionTree(Team[] teams, int timeSlotCount)
|
||||
{
|
||||
_timeSlotCount = timeSlotCount;
|
||||
_teams = teams;
|
||||
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||
//_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||
}
|
||||
|
||||
public IList<Team>[] Solve()
|
||||
public TeamSchedulerSolution Solve()
|
||||
{
|
||||
var timeSlots = new IList<Team>[_timeSlotCount];
|
||||
for (var i = 0; i < _timeSlotCount; i++)
|
||||
@@ -34,68 +33,68 @@ public class TeamScheduler_DecisionTree
|
||||
|
||||
timeSlots[overlaps.First().Item1].Add(team);
|
||||
}
|
||||
return timeSlots;
|
||||
return new TeamSchedulerSolution(timeSlots.Select(e => e.ToArray()).ToArray(), "Success?");
|
||||
}
|
||||
|
||||
public IList<Team>[] SolveRecursive()
|
||||
{
|
||||
// initialize time slots
|
||||
var timeSlots = new IList<Team>[_timeSlotCount];
|
||||
for (var i = 0; i < _timeSlotCount; i++)
|
||||
timeSlots[i] = new List<Team>();
|
||||
//public Team[][] SolveRecursive()
|
||||
//{
|
||||
// // initialize time slots
|
||||
// var timeSlots = new Team[][_timeSlotCount];
|
||||
// for (var i = 0; i < _timeSlotCount; i++)
|
||||
// timeSlots[i] = [];
|
||||
|
||||
return
|
||||
(from i in Enumerable.Range(1, 5)
|
||||
let solution = Recursive(timeSlots, _teams, i)
|
||||
let overlapCount = Team.GetStudentTeamOverlapCount(solution)
|
||||
orderby overlapCount
|
||||
select solution).First();
|
||||
}
|
||||
// return
|
||||
// (from i in Enumerable.Range(1, 5)
|
||||
// let solution = Recursive(timeSlots, _teams, i)
|
||||
// let overlapCount = TeamSchedulerSolution.GetStudentTeamOverlapCount(solution.Select(e => e.ToArray()).ToArray())
|
||||
// orderby overlapCount
|
||||
// select solution).First();
|
||||
//}
|
||||
|
||||
private static IList<Team>[] CopyTimeSlots(IList<Team>[] timeSlots)
|
||||
{
|
||||
return timeSlots.Select(ts => (IList<Team>)new List<Team>(ts)).ToArray();
|
||||
}
|
||||
//private static Team[][] CopyTimeSlots(Team[][] timeSlots)
|
||||
//{
|
||||
// return timeSlots.Select(Team[] (ts) => ts.ToArray()).ToArray();
|
||||
//}
|
||||
|
||||
private static IList<Team>[] Recursive(IList<Team>[] timeSlots, IList<Team> teams, int overlapTriesStudents = 4)
|
||||
{
|
||||
if (!teams.Any())
|
||||
return timeSlots;
|
||||
|
||||
var overlapsTries =
|
||||
(from team in teams.OrderByDescending(t => t.Students.Count).Take(overlapTriesStudents)
|
||||
//from ts in timeSlots
|
||||
from tsi in Enumerable.Range(0, timeSlots.Length)
|
||||
let ts = timeSlots[tsi]
|
||||
let tss = ts.SelectMany(t => t.Students)
|
||||
select Tuple.Create(ts, tsi, team, team.Students.Count(tss.Contains)))
|
||||
.OrderBy(t => t.Item4)
|
||||
.ThenBy(t => t.Item1.Count);
|
||||
//private static Team[][] Recursive(Team[][] timeSlots, Team[] teams, int overlapTriesStudents = 4)
|
||||
//{
|
||||
// if (!teams.Any())
|
||||
// return timeSlots;
|
||||
|
||||
var minOverlaps =
|
||||
(from o in overlapsTries
|
||||
group o by o.Item4
|
||||
into oo
|
||||
orderby oo.Key
|
||||
select oo).First();
|
||||
// var overlapsTries =
|
||||
// (from team in teams.OrderByDescending(t => t.Students.Count).Take(overlapTriesStudents)
|
||||
// //from ts in timeSlots
|
||||
// from tsi in Enumerable.Range(0, timeSlots.Length)
|
||||
// let ts = timeSlots[tsi]
|
||||
// let tss = ts.SelectMany(t => t.Students)
|
||||
// select Tuple.Create(ts, tsi, team, team.Students.Count(tss.Contains)))
|
||||
// .OrderBy(t => t.Item4)
|
||||
// .ThenBy(t => t.Item1.Length);
|
||||
|
||||
var first = minOverlaps.First();
|
||||
// var minOverlaps =
|
||||
// (from o in overlapsTries
|
||||
// group o by o.Item4
|
||||
// into oo
|
||||
// orderby oo.Key
|
||||
// select oo).First();
|
||||
|
||||
var results = new List<Tuple<IList<Team>[], int>>();
|
||||
// var first = minOverlaps.First();
|
||||
|
||||
foreach (var minOverlap in minOverlaps)
|
||||
{
|
||||
var timeSlotsAfterAdd = CopyTimeSlots(timeSlots);
|
||||
timeSlotsAfterAdd[first.Item2].Add(first.Item3);
|
||||
var remainingTeams = teams.Where(t => t != first.Item3).ToList();
|
||||
// var results = new List<Tuple<List<Team>[]>, int>>();
|
||||
|
||||
var result = Recursive(timeSlotsAfterAdd, remainingTeams);
|
||||
// foreach (var minOverlap in minOverlaps)
|
||||
// {
|
||||
// var timeSlotsAfterAdd = CopyTimeSlots(timeSlots);
|
||||
// timeSlotsAfterAdd[first.Item2].Add(first.Item3);
|
||||
// var remainingTeams = teams.Where(t => t != first.Item3).ToArray();
|
||||
|
||||
results.Add(Tuple.Create(result, Team.GetStudentTeamOverlapCount(result)));
|
||||
if (minOverlap.Item4 == 0)
|
||||
break;
|
||||
}
|
||||
// var result = Recursive(timeSlotsAfterAdd.Select(e => e.ToArray()).ToArray(), remainingTeams);
|
||||
|
||||
return results.OrderByDescending(r => r.Item2).First().Item1;
|
||||
}
|
||||
// results.Add(Tuple.Create(result, TeamSchedulerSolution.GetStudentTeamOverlapCount(result)));
|
||||
// if (minOverlap.Item4 == 0)
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return results.OrderByDescending(r => r.Item2).First().Item1;
|
||||
//}
|
||||
}
|
||||
@@ -4,49 +4,42 @@ namespace Core.Calculation;
|
||||
|
||||
public class UnassignedStudentScheduler
|
||||
{
|
||||
private readonly IList<Student> _students;
|
||||
private readonly IList<Team> _teams;
|
||||
private readonly IList<Team>[] _timeslots;
|
||||
private readonly Student[] _students;
|
||||
private readonly Team[] _teams;
|
||||
private readonly IList<Team>[] _timeSlots;
|
||||
|
||||
public UnassignedStudentScheduler(IList<Team> teams, IList<Team>[] timeslots)
|
||||
public UnassignedStudentScheduler(Team[] teams, Team[][] timeslots)
|
||||
{
|
||||
_teams = teams;
|
||||
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
|
||||
_timeslots = timeslots.Select(ts => ts.Select(t => t.Clone()).ToList()).ToArray();
|
||||
_students = teams.SelectMany(t => t.Students).Distinct().ToArray();
|
||||
_timeSlots = timeslots.Select(ts => ts.Select(t => t.Clone()).ToArray()).ToArray();
|
||||
}
|
||||
public static IEnumerable<Student> UnassignedStudents(IList<Student> students, IList<Team> timeSlot)
|
||||
=> students.Where(s => !timeSlot.SelectMany(t => t.Students).Contains(s));
|
||||
|
||||
|
||||
public static IEnumerable<Student>[] UnassignedStudents(IList<Student> students, IList<Team>[] schedule)
|
||||
=> schedule.Select(ts => UnassignedStudents(students, ts)).ToArray();
|
||||
|
||||
public TeamSchedulerSolution ScheduleStrategy(UnassignedScheduleStrategy scheduleStrategy)
|
||||
{
|
||||
var ss = scheduleStrategy switch
|
||||
{
|
||||
UnassignedScheduleStrategy.BiggestGroup => ScheduleStrategy(GetAvailableTeams_BiggestGroup),
|
||||
UnassignedScheduleStrategy.IndividualEvents => ScheduleStrategy(GetAvailableTeams_Individual),
|
||||
UnassignedScheduleStrategy.AnyNotMeetingAlready => ScheduleStrategy(GetAvailableTeams_AnyNotMeetingAlready),
|
||||
UnassignedScheduleStrategy.Any => ScheduleStrategy(GetAvailableTeams_Any),
|
||||
UnassignedScheduleStrategy.LevelOfEffort => ScheduleStrategy(GetAvailableTeams_LevelOfEffort),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(scheduleStrategy), scheduleStrategy, null)
|
||||
};
|
||||
|
||||
public IList<Team>[] ScheduleStrategy(UnassignedScheduleStrategy scheduleStrategy)
|
||||
{
|
||||
switch (scheduleStrategy)
|
||||
{
|
||||
case UnassignedScheduleStrategy.BiggestGroup:
|
||||
return ScheduleStrategy(GetAvailableTeams_BiggestGroup);
|
||||
case UnassignedScheduleStrategy.IndividualEvents:
|
||||
return ScheduleStrategy(GetAvailableTeams_Individual);
|
||||
case UnassignedScheduleStrategy.AnyNotMeetingAlready:
|
||||
return ScheduleStrategy(GetAvailableTeams_AnyNotMeetingAlready);
|
||||
case UnassignedScheduleStrategy.Any:
|
||||
return ScheduleStrategy(GetAvailableTeams_Any);
|
||||
case UnassignedScheduleStrategy.LevelOfEffort:
|
||||
return ScheduleStrategy(GetAvailableTeams_LevelOfEffort);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(scheduleStrategy), scheduleStrategy, null);
|
||||
}
|
||||
}
|
||||
return new TeamSchedulerSolution(ss, "Success?");
|
||||
}
|
||||
|
||||
|
||||
public IList<Team>[] ScheduleStrategy(Func<IEnumerable<Team>, IEnumerable<Student>, IEnumerable<Team>> availableTeamSelector)
|
||||
public Team[][] ScheduleStrategy(Func<IEnumerable<Team>, IEnumerable<Student>, IEnumerable<Team>> availableTeamSelector)
|
||||
{
|
||||
// Find stuff for unassigned students in each timeslot
|
||||
var scheduledTeams = _timeslots.SelectMany(list => list).Distinct().ToList();
|
||||
foreach (var slot in _timeslots)
|
||||
var scheduledTeams = _timeSlots.SelectMany(list => list).Distinct().ToList();
|
||||
foreach (var slot in _timeSlots)
|
||||
{
|
||||
var unassigned = UnassignedStudents(_students, slot).ToList();
|
||||
while (unassigned.Count > 0)
|
||||
@@ -67,15 +60,14 @@ public class UnassignedStudentScheduler
|
||||
}
|
||||
}
|
||||
|
||||
return _timeslots;
|
||||
return _timeSlots.Select(e => e.ToArray()).ToArray();
|
||||
}
|
||||
|
||||
|
||||
// find teams where several unassigned students can work together
|
||||
private IEnumerable<Team> GetAvailableTeams_BiggestGroup(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||
@@ -86,7 +78,7 @@ public class UnassignedStudentScheduler
|
||||
private IEnumerable<Team> GetAvailableTeams_Individual(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||
.Where(t => t.Event.EventFormat == EventFormat.Individual || t.Students.Count == 1)
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 0);
|
||||
@@ -95,7 +87,7 @@ public class UnassignedStudentScheduler
|
||||
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 0);
|
||||
|
||||
@@ -110,7 +102,7 @@ public class UnassignedStudentScheduler
|
||||
private IEnumerable<Team> GetAvailableTeams_LevelOfEffort(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Where(t => scheduledTeams.All(st => st.TeamId != t.TeamId))
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||
@@ -120,38 +112,38 @@ public class UnassignedStudentScheduler
|
||||
public IList<Team>[] AddAdditionalTimeSlot(Team team)
|
||||
{
|
||||
// sort by how many teammembers are already in that timeslot, descending
|
||||
foreach (var timeslot in _timeslots.OrderBy(ts => ts.SelectMany(t => t.Students).Count(team.Students.Contains)))
|
||||
foreach (var timeslot in _timeSlots.OrderBy(ts => ts.SelectMany(t => t.Students).Count(team.Students.Contains)))
|
||||
{
|
||||
if (timeslot.Any(t => t.Name == team.Name))
|
||||
if (timeslot.Any(t => t.TeamId == team.TeamId))
|
||||
continue;
|
||||
timeslot.Add(team);
|
||||
break;
|
||||
}
|
||||
|
||||
return _timeslots;
|
||||
return _timeSlots;
|
||||
}
|
||||
|
||||
// Keep the current events rolling for the other time slots
|
||||
public IList<Team>[] ExtendEvents()
|
||||
{
|
||||
var scheduledTeams = _timeslots.SelectMany(list => list).Distinct().ToList();
|
||||
var scheduledTeams = _timeSlots.SelectMany(list => list).Distinct().ToList();
|
||||
|
||||
// get all the students in each time slot
|
||||
var timeslotStudents = _timeslots.Select(ts => ts.SelectMany(t => t.Students).Distinct()).ToArray();
|
||||
var timeslotStudents = _timeSlots.Select(ts => ts.SelectMany(t => t.Students).Distinct()).ToArray();
|
||||
|
||||
// clone teams from timeslot for each other timeslot, removing the students
|
||||
|
||||
//var copiedTeams = new Dictionary<int, IList<Team>>();
|
||||
|
||||
for (var i = 0; i < _timeslots.Length; i++)
|
||||
for (var i = 0; i < _timeSlots.Length; i++)
|
||||
{
|
||||
var sourceTimeslot = _timeslots[i];
|
||||
var sourceTimeslot = _timeSlots[i];
|
||||
|
||||
for (var j = 0; j < _timeslots.Length; j++)
|
||||
for (var j = 0; j < _timeSlots.Length; j++)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
var targetTimeslot = _timeslots[j];
|
||||
var targetTimeslot = _timeSlots[j];
|
||||
var targetTimeslotStudents = targetTimeslot.SelectMany(t => t.Students).Distinct();
|
||||
var clonedTeams = sourceTimeslot.Select(t => t.CloneWithOmittedStudents(targetTimeslotStudents));
|
||||
foreach (var clonedTeam in clonedTeams.Where(t => t.Students.Any()))
|
||||
@@ -161,6 +153,6 @@ public class UnassignedStudentScheduler
|
||||
}
|
||||
}
|
||||
|
||||
return _timeslots;
|
||||
return _timeSlots;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,10 @@ public class EventDefinition
|
||||
|| SemifinalistActivity.Contains("Speech")
|
||||
|| SemifinalistActivity.Contains("Test")
|
||||
|| SemifinalistActivity.Contains("Flight")
|
||||
|| Name.Contains("Leadership")
|
||||
|| Name.Contains("Forensic")
|
||||
|| Name.Contains("Flight")
|
||||
|| Name.Contains("Coding")
|
||||
|| SemifinalistActivity.Contains("Debate")
|
||||
|| SemifinalistActivity.Contains("Photography")
|
||||
|| SemifinalistActivity.Contains("Build")
|
||||
|
||||
@@ -8,6 +8,6 @@ public class PartialTeam : Team
|
||||
{
|
||||
var remainingStudents = Students.Where(s => !studentsToOmit.Contains(s)).ToList();
|
||||
var omittedStudents = OmittedStudents.Union(Students.Where(studentsToOmit.Contains)).Distinct().ToList();
|
||||
return new PartialTeam{TeamId = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents };
|
||||
return new PartialTeam{TeamId = TeamId, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents };
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using static System.Text.RegularExpressions.Regex;
|
||||
|
||||
namespace Core.Entities;
|
||||
|
||||
public class Student
|
||||
public class Student : IEquatable<Student>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
@@ -74,4 +74,23 @@ public class Student
|
||||
|
||||
public bool VotingDelegate => OfficerRole is Entities.OfficerRole.President or Entities.OfficerRole.VicePresident;
|
||||
|
||||
public bool Equals(Student? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((Student)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ public class StudentEventRanking
|
||||
public EventDefinition EventDefinition { get; set; } = null!;
|
||||
public int Rank { get; set; }
|
||||
|
||||
public const int MaxRank = 6;
|
||||
public const int MaxRank = 10;
|
||||
|
||||
}
|
||||
+11
-37
@@ -5,10 +5,15 @@ public class Team
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[Required]
|
||||
[Display(Name = "Team Name")]
|
||||
public string Name { get; set; }
|
||||
public EventDefinition Event { get; set; }
|
||||
public int? Number { get; set; }
|
||||
public EventDefinition Event
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<Student> Students { get; set; } = [];
|
||||
|
||||
public Student? Captain { get; set; }
|
||||
@@ -16,8 +21,6 @@ public class Team
|
||||
[Display(Name = "Team Id")]
|
||||
public string? TeamId { get; set; }
|
||||
|
||||
//public string? RegionalTimeSlot { get; set; }
|
||||
|
||||
|
||||
// public Tuple<DateTime,DateTime?>? RegionalTimeSlotObj
|
||||
//{
|
||||
@@ -51,46 +54,17 @@ public class Team
|
||||
var studentsToOmitList = studentsToOmit.ToList();
|
||||
var omittedStudents = Students.Where(studentsToOmitList.Contains).ToList();
|
||||
if (!omittedStudents.Any())
|
||||
return new Team{Captain = Captain, Event = Event, Students = Students.ToList(), TeamId = Name};
|
||||
return new Team{Captain = Captain, Event = Event, Students = Students.ToList(), TeamId = TeamId, Number = Number};
|
||||
|
||||
var remainingStudents = Students.Where(s => !studentsToOmitList.Contains(s)).ToList();
|
||||
return new PartialTeam { Name = Name, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents};
|
||||
return new PartialTeam { Number = Number, Event = Event, Students = remainingStudents, OmittedStudents = omittedStudents};
|
||||
}
|
||||
|
||||
public Team Clone() => CloneWithOmittedStudents([]);
|
||||
|
||||
public static int GetStudentTeamOverlapCount(IList<Team>[] timeSlots)
|
||||
{
|
||||
return timeSlots.Sum(GetStudentTeamOverlapCount);
|
||||
}
|
||||
|
||||
private static int GetStudentTeamOverlapCount(IList<Team> timeSlot)
|
||||
{
|
||||
return GetStudentTeamOverlaps(timeSlot).Count();
|
||||
}
|
||||
|
||||
public static IEnumerable<Tuple<Student, IEnumerable<Team>>> GetStudentTeamOverlaps(IList<Team> timeSlot)
|
||||
{
|
||||
return
|
||||
from s in timeSlot.SelectMany(ts => ts.Students).Distinct()
|
||||
group s by timeSlot.Where(t => t.Students.Contains(s))
|
||||
into gs
|
||||
where gs.Key.Count() > 1
|
||||
select Tuple.Create(gs.First(), gs.Key);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string ToStringWithIndividualAndRegional()
|
||||
{
|
||||
var ind = Event.EventFormat is EventFormat.Individual ? " (Ind.)" : string.Empty;
|
||||
var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
|
||||
//var regional= Event.RegionalEvent ? " (Reg.)" : string.Empty;
|
||||
|
||||
var eventAttributes = Event.EventAttributes();
|
||||
return string.IsNullOrEmpty(eventAttributes) ? Name : Name + " (" + eventAttributes + ")";
|
||||
return $"{Event.Name} {(Number != null ? $"(#{Number})" : "")}";
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class EventDefinitionParser : CsvParserBase
|
||||
var theme = CsvReader.GetField("Theme");
|
||||
var description = CsvReader.GetField("Description");
|
||||
var levelOfEffort = CsvReader.GetField<int?>("Level of Effort");
|
||||
//var regionalTeams = CsvReader.GetField<int>("Regional Teams");
|
||||
//var regionalTeams = CsvReader.GetField<int>("Regional TimeSlots");
|
||||
|
||||
var competitiveEvent = new EventDefinition
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Core.Parsers
|
||||
|
||||
foreach (var team in _teams)
|
||||
{
|
||||
csv.WriteField(team.Name);
|
||||
csv.WriteField(team.TeamId);
|
||||
csv.WriteField(team.Event.Name);
|
||||
foreach (var teamStudent in team.Students)
|
||||
{
|
||||
@@ -146,14 +146,14 @@ namespace Core.Parsers
|
||||
{
|
||||
if (@event.EventFormat is EventFormat.Team)
|
||||
{
|
||||
teams.Add(new Team { Event = @event, Students = teamStudents, Captain = captain, Name = teamName, TeamId = teamNumber});
|
||||
teams.Add(new Team { Event = @event, Students = teamStudents, Captain = captain, TeamId = teamNumber});
|
||||
}
|
||||
else if (@event.EventFormat is EventFormat.Individual)
|
||||
{
|
||||
foreach (var student in teamStudents)
|
||||
{
|
||||
teams.Add(new Team{Name = $"{teamName} - {student.FirstName}", Event = @event,
|
||||
Students = new List<Student> { student }, Captain = student, TeamId = teamNumber});
|
||||
teams.Add(new Team{Event = @event,
|
||||
Students = [student], Captain = student, TeamId = teamNumber});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user