first commit

This commit is contained in:
2025-08-01 14:10:44 -04:00
commit cf32cfcbcd
149 changed files with 80416 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
using Core.Entities;
namespace Core.Calculation;
public class DataProcessing
{
public static EventStudentPicks[] GetEventStudentPicks(IList<CompetitiveEvent> events, IList<Student> students)
{
return
students.SelectMany(
student => student.RankedEventPicks.Select((e, i) => (e, student, i + 1)))
.OrderBy(tuple => tuple.Item3)
.ThenByDescending(tuple => tuple.student.Grade + tuple.student.TsaYear)
.GroupBy(tuple => tuple.e)
.OrderBy(tuples => tuples.Key.Name)
.Select(tuples =>
new EventStudentPicks(tuples.Key, tuples.Select(tuple => Tuple.Create(tuple.student, tuple.Item3)).ToList())
).ToArray();
}
}
+315
View File
@@ -0,0 +1,315 @@
using System.Diagnostics;
using Core.Entities;
using Google.OrTools.Sat;
using IntVar = Google.OrTools.Sat.IntVar;
namespace Core.Calculation
{
public class EventAssigner
{
private readonly IList<CompetitiveEvent> _events;
private readonly IList<Student> _students;
private readonly AssignmentParameters _parameters;
private readonly int[] _allEvents;
private readonly int[] _allStudents;
// how many students have picked each event?
private readonly int[] _eventPickCounts;
private IList<EventAssignment> _preAssigned = new List<EventAssignment>();
private IList<EventAssignment> _prohibited = new List<EventAssignment>();
private IList<CompetitiveEvent> _droppedEvents;
private IList<CompetitiveEvent> _includedEvents;
public EventAssigner(IList<CompetitiveEvent> events, IList<Student> students, AssignmentParameters parameters)
{
_events = events;
_students = students;
_parameters = parameters;
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
_allStudents = Enumerable.Range(0, _students.Count).ToArray();
_eventPickCounts = new int[_allEvents.Length];
_preAssigned = new List<EventAssignment>();
_droppedEvents = new List<CompetitiveEvent>();
for (var i = 0; i < _events.Count; i++)
{
var e = _events[i];
_eventPickCounts[i] = _students.Count(s => s.RankedEventPicks.Contains(e));
}
}
public void AssignToEvent(EventAssignment preAssigned)
{
_preAssigned.Add(preAssigned);
}
public void ExcludeFromEvent(EventAssignment prohibited)
{
_prohibited.Add(prohibited);
}
public void RemoveEvent(IList<CompetitiveEvent> events)
{
_droppedEvents = events;
}
public void IncludedEvents(IList<CompetitiveEvent> events)
{
_includedEvents = events;
}
public class SolutionPrinter : CpSolverSolutionCallback
{
private readonly IList<CompetitiveEvent> _events;
private readonly IList<Student> _students;
private readonly BoolVar[,] _eventAssignment;
public SolutionPrinter(IList<CompetitiveEvent> events, IList<Student> students, BoolVar[,] eventAssignment)
{
_events = events;
_students = students;
_eventAssignment = eventAssignment;
}
public override void OnSolutionCallback()
{
Console.WriteLine($"Solution ");
foreach (var evt in Enumerable.Range(0,_events.Count))
{
foreach (var student in Enumerable.Range(0, _students.Count))
{
if (Value(_eventAssignment[evt, student]) == 1L)
{
}
}
}
}
}
public Team[] Solve()
{
// Model.
var model = new CpModel();
// Variables.
var x = new BoolVar[_allEvents.Length, _allStudents.Length];
foreach (var e in _allEvents)
foreach (var s in _allStudents)
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
foreach (var preAssignment in _preAssigned)
{
var e = _events.IndexOf(preAssignment.Event);
var s = _students.IndexOf(preAssignment.Student);
model.AddAssumption(x[e, s]);
}
foreach (var prohibit in _prohibited)
{
var e = _events.IndexOf(prohibit.Event);
var s = _students.IndexOf(prohibit.Student);
var prohibitVar = new List<IntVar> { x[e, s] };
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
prohibitVar.Clear();
}
// Limit the capacity of each event
foreach (var e in _allEvents)
{
var evt = _events[e];
var eventPickCounts = _eventPickCounts[e];
var evtMinTeamSize = evt.MinTeamSize;
var evtMaxTeamSize = evt.MaxTeamSize;
var teamDivs = eventPickCounts / (evtMinTeamSize * 1.0);
if (_includedEvents.Contains(evt))
teamDivs = 1;
//var teamsCount = (int)Math.Ceiling(teamDivs);
var teamCount = (int)Math.Round(teamDivs);
if (teamCount > evt.MaxTeamCountState)
teamCount = evt.MaxTeamCountState;
// limit to one team for group events
if (_parameters.LimitTeamsToOne && evt.Format is EventFormat.Team && teamCount > 1) teamCount = 1;
if (evt.Name == "Tech Bowl")
teamCount = 1;
var eventCapacity = new List<IntVar>();
foreach (var s in _allStudents)
{
eventCapacity.Add(x[e, s]);
}
if (_droppedEvents != null && _droppedEvents.Contains(evt))
teamCount = 0;
var lb = evtMinTeamSize * teamCount;
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit);
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
Debug.WriteLine($"{evt.Name,30}\t{evt.Format,-10}\t{lb} - {ub}");
model.Minimize(LinearExpr.Sum(eventCapacity));
eventCapacity.Clear();
}
// Limit the number of events a student is assigned
foreach (var s in _allStudents)
{
var student = _students[s];
var studentCapacity = new List<IntVar>();
foreach (var e in _allEvents)
{
studentCapacity.Add(x[e, s]);
}
model.AddLinearConstraint(LinearExpr.Sum(studentCapacity), _parameters.AssignmentLowerBound, _parameters.AssignmentUpperBound);
studentCapacity.Clear();
}
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
foreach (var s in _allStudents)
{
var effortVar = new BoolVar[_allEvents.Length];
foreach (var e in _allEvents)
{
effortVar[e] = x[e, s];
}
var student = _students[s];
var experienceOffset = 0;
switch (student.TsaYear)
{
case 1: experienceOffset = 1; break;
default: break;
}
var ub = _parameters.EffortUpperBound - experienceOffset;
var lb = _parameters.EffortLowerBound;
if (ub <= lb)
lb = ub - 1;
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) >= lb);
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) <= ub);
}
// each student should be assigned at least one on site activity event
foreach (var s in _allStudents)
{
var onSiteActivity = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].OnSiteActivity)
onSiteActivity.Add(x[e, s]);
}
if (_parameters.RequireOnSite)
model.AddAtLeastOne(onSiteActivity);
onSiteActivity.Clear();
}
// students should have at maximum one individual event
foreach (var s in _allStudents)
{
var individualEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].Format == EventFormat.Individual)
individualEvent.Add(x[e, s]);
}
model.AddAtMostOne(individualEvent);
individualEvent.Clear();
}
// students should have at maximum regional events
foreach (var s in _allStudents)
{
var regionalEvent = new List<ILiteral>();
foreach (var e in _allEvents)
{
if (_events[e].RegionalEvent)
regionalEvent.Add(x[e, s]);
}
if (_parameters.RequireRegional)
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
regionalEvent.Clear();
}
var maximizePicks = LinearExpr.NewBuilder();
// optimize student selections
foreach (var s in _allStudents)
{
var eventPickCoefficients = GetEventPickCoefficients(_students[s].RankedEventPicks, _events);
foreach (var e in _allEvents)
{
maximizePicks.AddTerm(x[e, s], eventPickCoefficients[e]);
}
}
model.Maximize(maximizePicks);
var solver = new CpSolver();
var cpSolverStatus = solver.Solve(model);
// print solver status
Console.WriteLine($"Solver status: {cpSolverStatus}");
var eventAssignmentsList = new List<Team>();
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
{
foreach (var e in _allEvents)
{
var students = new List<Student>();
foreach (var s in _allStudents)
{
if (solver.BooleanValue(x[e, s]))
{
students.Add(_students[s]);
//Console.WriteLine($"{_events[evt].Name} : {_students[s].Name}");
}
}
if (students.Count > 0)
eventAssignmentsList.Add(new Team(_events[e].Name, _events[e], students));
}
}
else
{
//Console.WriteLine("No solution found.");
}
return eventAssignmentsList.ToArray();
}
private long[] GetEventPickCoefficients(IList<CompetitiveEvent> rankedEvents, IEnumerable<CompetitiveEvent> events)
{
var eventPickCount = rankedEvents.Count;
return
events.Select(e =>
{
var eventPickIndex = rankedEvents.IndexOf(e);
return eventPickIndex switch
{
0 => eventPickCount + 1,
1 => eventPickCount + 0,
> 1 => eventPickCount ,
_ => 0L
};
}).ToArray();
}
private long[] GetEventEffortCoefficients(IEnumerable<CompetitiveEvent> events)
{
return
events.Select(e => e.Name == "Tech Bowl" ? 0 : e.LevelOfEffort.HasValue ? e.LevelOfEffort.Value : 10L).ToArray();
}
}
}
+136
View File
@@ -0,0 +1,136 @@
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 int[] _students;
private readonly int[] _teams;
private readonly int[] _timeSlots;
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = new ();
public TeamScheduler(IList<Team> teams, int numTimeSlots)
{
_teamObjs = teams;
_studentObjs = teams.SelectMany(t => t.Students).Distinct().ToList();
_students = Enumerable.Range(0, _studentObjs.Count).ToArray();
_teams = Enumerable.Range(0, _teamObjs.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);
_scheduleSeparateTeams.Add(Tuple.Create(one,two));
}
public static TeamScheduler CreateInstance(IList<Team> teams, int numTimeSlots)
{
return new TeamScheduler(teams, numTimeSlots);
}
public IList<Team>[] Solve()
{
// Model.
var model = new CpModel();
// Data
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;
// Variables.
// x - 1 if meeting of team t takes place at time slot s, else 0
var x = new IntVar[_teams.Length, _timeSlots.Length];
foreach (var t in _teams)
foreach (var s in _timeSlots)
x[t, s] = model.NewIntVar(0, 1,$"team time slots[{t},{s}]");
// y - 1 if individual i has meetings at time slot s, 0 otherwise
var y = new IntVar[_students.Length, _timeSlots.Length];
foreach (var i in _students)
foreach (var s in _timeSlots)
y[i, s] = model.NewIntVar(0, 1, $"individual time slots[{i},{s}]");
// each team meets exactly one time
foreach (var t in _teams)
model.AddLinearConstraint(LinearExpr.Sum(_timeSlots.Select(s => x[t, s])), 1L, 1L);
// individual must have at least one team meeting at the given time slot to attend
foreach (var i in _students)
foreach (var s in _timeSlots)
model.Add(
new BoundedLinearExpression(
y[i, s],
LinearExpr.Sum(_teams.Select(t => m[i, t] * x[t, s])),
false));
// maximize number of times individuals meet
var indTimeSlotVars = LinearExpr.NewBuilder();
foreach (var i in _students)
foreach (var s in _timeSlots)
indTimeSlotVars.Add(y[i, s]);
model.Minimize(indTimeSlotVars);
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}");
if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible)
{
Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
//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>>();
}
}
}
@@ -0,0 +1,101 @@
using Core.Entities;
namespace Core.Calculation;
public class TeamScheduler_DecisionTree
{
private readonly IList<Student> _students;
private readonly IList<Team> _teams;
private readonly int _timeSlotCount;
public TeamScheduler_DecisionTree(IList<Team> teams, int timeSlotCount)
{
_timeSlotCount = timeSlotCount;
_teams = teams;
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
}
public IList<Team>[] Solve()
{
var timeSlots = new IList<Team>[_timeSlotCount];
for (var i = 0; i < _timeSlotCount; i++)
timeSlots[i] = new List<Team>();
foreach (var team in _teams.OrderByDescending(t => t.Students.Count))
{
// get overlapping students in each timeslot
var overlaps
= (from tsi in Enumerable.Range(0, timeSlots.Length)
let ts = timeSlots[tsi]
let tss = ts.SelectMany(t => t.Students).Distinct()
select Tuple.Create(tsi, team.Students.Count(tss.Contains)))
.OrderBy(t => t.Item2)
.ThenBy(t => timeSlots[t.Item1].Count);
timeSlots[overlaps.First().Item1].Add(team);
}
return timeSlots;
}
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>();
return
(from i in Enumerable.Range(1, 5)
let solution = Recursive(timeSlots, _teams, i)
let overlapCount = Team.GetStudentTeamOverlapCount(solution)
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 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);
var minOverlaps =
(from o in overlapsTries
group o by o.Item4
into oo
orderby oo.Key
select oo).First();
var first = minOverlaps.First();
var results = new List<Tuple<IList<Team>[], int>>();
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 result = Recursive(timeSlotsAfterAdd, remainingTeams);
results.Add(Tuple.Create(result, Team.GetStudentTeamOverlapCount(result)));
if (minOverlap.Item4 == 0)
break;
}
return results.OrderByDescending(r => r.Item2).First().Item1;
}
}
@@ -0,0 +1,93 @@
using Core.Entities;
using Core.Utility;
using Google.OrTools.Sat;
namespace Core.Calculation;
public class TeamScheduler_Prototype
{
public void Solve()
{
// Model.
var model = new CpModel();
// Data
int[,] m =
{
{ 1, 0, 0, 0, 0, 1 }, // 1
{ 0, 0, 1, 0, 0, 0 }, // 2
{ 0, 0, 1, 0, 1, 0 }, // 3
{ 0, 0, 0, 0, 1, 1 }, // 4
{ 0, 0, 1, 0, 0, 0 }, // 5
{ 1, 0, 1, 0, 0, 0 }, // 6
{ 0, 0, 0, 0, 0, 1 }, // 7
{ 0, 1, 0, 1, 0, 0 }, // 8
{ 0, 0, 0, 0, 0, 0 }, // 9
{ 1, 1, 0, 0, 1, 0 }, // 10
{ 1, 1, 0, 0, 1, 1 }, // 11
{ 0, 0, 0, 0, 1, 1 }, // 12
{ 1, 0, 0, 0, 0, 1 }, // 13
{ 0, 1, 1, 1, 0, 0 }, // 14
{ 1, 1, 0, 0, 0, 0 }, // 15
{ 0, 0, 0, 0, 0, 0 }, // 16
{ 1, 0, 1, 0, 1, 1 }, // 17
{ 0, 0, 0, 1, 0, 0 }, // 18
{ 1, 0, 0, 1, 0, 0 }, // 19
{ 0, 0, 1, 0, 1, 0 }, // 20
};
// Variables.
int[] individuals = Enumerable.Range(0, 20).ToArray();
int[] teams = Enumerable.Range(0, 6).ToArray();
int[] timeSlots = Enumerable.Range(0, 3).ToArray();
// x - 1 if meeting of team t takes place at time slot s, else 0
var x = new IntVar[teams.Length, timeSlots.Length];
foreach (var t in teams)
foreach (var s in timeSlots)
x[t, s] = model.NewIntVar(0, 1,$"team time slots[{t},{s}]");
// y - 1 if individual i has meetings at time slot s, 0 otherwise
var y = new IntVar[individuals.Length, timeSlots.Length];
foreach (var i in individuals)
foreach (var s in timeSlots)
y[i, s] = model.NewIntVar(0, 1, $"individual time slots[{i},{s}]");
// each team meets exactly one time
foreach (var t in teams)
model.AddLinearConstraint(LinearExpr.Sum(timeSlots.Select(s => x[t, s])), 1L, 1L);
//individual must have at least one team meeting at the given time slot to attend
foreach (var i in individuals)
foreach (var s in timeSlots)
model.Add(
new BoundedLinearExpression(
y[i, s],
LinearExpr.Sum(teams.Select(t => m[i, t] * x[t, s])),
false));
// maximize number of times individuals meet
var indTimeSlotVars = LinearExpr.NewBuilder();
foreach (var i in individuals)
foreach (var s in timeSlots)
indTimeSlotVars.Add(y[i, s]);
model.Minimize(indTimeSlotVars);
var solver = new CpSolver();
var cpSolverStatus = solver.Solve(model);
Console.WriteLine($"Solver status: {cpSolverStatus}");
if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible)
{
Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n");
Console.WriteLine("Team's Timeslots");
TextUtil.ConsoleWriteTable((t, s) => solver.Value(x[t, s]) > 0, "team", teams, "timeslot", timeSlots);
Console.WriteLine("Individual's Timeslots");
TextUtil.ConsoleWriteTable((i, s) => solver.Value(y[i, s]) > 0, "ind", individuals, "timeslot", timeSlots);
}
else
{
Console.WriteLine("No solution found.");
}
}
}
@@ -0,0 +1,175 @@
using Core.Entities;
namespace Core.Calculation;
public enum UnassignedScheduleStrategy
{
BiggestGroup,
IndividualEvents,
AnyNotMeetingAlready,
LevelOfEffort,
Any
}
public class UnassignedStudentScheduler
{
private readonly IList<Student> _students;
private readonly IList<Team> _teams;
private readonly IList<Team>[] _timeslots;
public UnassignedStudentScheduler(IList<Team> teams, IList<Team>[] timeslots)
{
_teams = teams;
_students = teams.SelectMany(t => t.Students).Distinct().ToList();
_timeslots = timeslots.Select(ts => ts.Select(t => t.Clone()).ToList()).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 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);
}
}
public IList<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 unassigned = UnassignedStudents(_students, slot).ToList();
while (unassigned.Count > 0)
{
var assignedStudents = slot.SelectMany(t => t.Students).Distinct();
var availableTeams = availableTeamSelector(scheduledTeams, assignedStudents);
var teamToAdd = availableTeams.FirstOrDefault();
if (teamToAdd == null)
break;
slot.Add(teamToAdd);
scheduledTeams.Add(teamToAdd);
foreach (var student in teamToAdd.Students)
unassigned.Remove(student);
}
}
return _timeslots;
}
// 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))
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
.OrderByDescending(t => t.Students.Count); // select descending greatest number of students assigned
//.ThenBy(t => Random.Shared.Next()); // todo: sort by student historic record of event assignment
// find individual events unassigned students can work on
private IEnumerable<Team> GetAvailableTeams_Individual(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Where(t => t.Event.Format == EventFormat.Individual || t.Students.Count == 1)
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 0);
// find any unassigned event students can work on
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 0);
private IEnumerable<Team> GetAvailableTeams_Any(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 0);
// find teams where several unassigned students can work together
private IEnumerable<Team> GetAvailableTeams_LevelOfEffort(
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
_teams
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
.OrderByDescending(t => t.Event.LevelOfEffort); // select descending greatest number of students assigned
//add team to another timeslot, if any available
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)))
{
if (timeslot.Any(t => t.Name == team.Name))
continue;
timeslot.Add(team);
break;
}
return _timeslots;
}
// Keep the current events rolling for the other time slots
public IList<Team>[] ExtendEvents()
{
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();
// 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++)
{
var sourceTimeslot = _timeslots[i];
for (var j = 0; j < _timeslots.Length; j++)
{
if (i == j)
continue;
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()))
{
targetTimeslot.Add(clonedTeam);
}
}
}
return _timeslots;
}
}