I have an application to develop at school that consists in generating work groups for a subject. The restrictions I have to fulfill are the following:
- Students having similar GPAs should be on the same group.
- Students that previously have worked together should be on different groups.
- Group Size should have a minimum and a maximum size possible.
My predicate currently looks like this:
groups(Students, GPAs, PreviouslyWorkedTogether, [MinSize,MaxSize],Groups).
Students is a list of student IDs (ex:. [1,2,3,4]).
GPAs is a list of GPAs (ex:.[4.0,3.5,2.0,3.7]).
The above lists relate to each other such that Student with ID = 1 has a GPA of 4.0, ID = 2 has GPA = 3.5, and so on.
PreviouslyWorkedTogether is a list of Pairs where each element has the IDs of two students that worked together before. (ex [ [1,3] , [2,4] ] - Student 1 worked with Student 3 and Student 2 worked with Student 4).
Groups is the desired result. It has the same size of the Students list. For each Student it should fill the variable with the group ID that the student belongs to. (ex:. [1,2,1,2] -> This means that students 1 and 3 are in group 1,students 2 and 4 are in group 2).
I already implemented successfully (I think) the Group Size boundaries. However I am having trouble with the GPA and WorkedTogether part.
As they seem very similar to each other i will only approach one of them.
The next code snippet is my current solve for the GPA problem.
getGPAs(_, [], []).
getGPAs(GPAs, [H|T], [GroupGPAsH | GroupGPAsT]):-
element(H, GPAs, GPA),
GroupGPAsH #= GPA,
getGPAs(GPAs, T, GroupGPAsT).
constrain_GPA(_, _, MaxGroupID, GroupID, []):- GroupID #> MaxGroupID, !.
constrain_GPA(GPAs, Groups, MaxGroupID, GroupID, [DiffsH | DiffsT]):-
findall(Index, element(Index,Groups,GroupID),GroupElems),
getGPAs(GPAs, GroupElems, GroupGPAs),
minimum(MinGPA, GroupGPAs),
maximum(MaxGPA, GroupGPAs),
NextID is GroupID + 1,
DiffsH #= MaxGPA - MinGPA,
constrain_GPA(GPAs, Groups, MaxGroupID, NextID, DiffsT).
Firstly I find all the indexes of the elements that belong to GroupID and store it in a list (GroupElems).
After that, I use those indexes to get the GPAs of each member of the group.
Then I just have to get the maximum and minimum values of the GPAs of the group and calculate the difference, storing it in a list containing the differences of GPAs of each group.
After that, all I do is minimize the sum of the array values so we get the result that contains the best result of groups with similar GPAs.
constrain_GPA(GPAs, Groups, MaxGroupID, 1, Diffs),
sum(Diffs, #=, SumDiffs),
labeling([minimize(SumDiffs)], Groups),
However it continues to give me an error on the labeling predicate. I believe the problem is the findall predicate. I don't know if i can use it or not.
The logic seems right however the coding is the problem.
Here's the complete code (Test query at the end)
:- use_module(library(clpfd)).
:- use_module(library(lists)).
groups(Students, GPAs, PreviousUCsInfo, [MinSize, MaxSize], Groups):-
%create an array representing the groups of each student
length(Students, NumStudents),
length(Groups, NumStudents),
MaxNumGroups is NumStudents div MinSize,
MinNumGroupsMod is NumStudents mod MaxSize,
if_then_else(
(MinNumGroupsMod = 0),
(MinNumGroups is NumStudents div MaxSize),
(MinNumGroups is (NumStudents div MaxSize) + 1)
),
domain([MaxGroupID], MinNumGroups, MaxNumGroups),
domain(Groups, 1, MaxNumGroups),
%constrain group size
nvalue(MaxGroupID, Groups),
constrain_count(Groups, [MinSize, MaxSize], MaxGroupID, 1),
%contrain GPA
constrain_GPA(GPAs, Groups, MaxGroupID, 1, Diffs),
sum(Diffs, #=, SumDiffs),
append(Groups, [MaxGroupID], LabelVars),
labeling([minimize(SumDiffs)], LabelVars).
constrain_count(_, _, MaxGroupID, GroupID):- GroupID #> MaxGroupID.
constrain_count(Groups, [MinSize, MaxSize], MaxGroupID, GroupID):-
count(GroupID, Groups, #=, Times),
Times #>= MinSize #/\ Times #=< MaxSize,
NextID is GroupID + 1,
constrain_count(Groups, [MinSize, MaxSize], MaxGroupID, NextID).
getGPAs(_, [], []).
getGPAs(GPAs, [H|T], [GroupGPAsH | GroupGPAsT]):-
element(H, GPAs, GPA),
GroupGPAsH #= GPA,
getGPAs(GPAs, T, GroupGPAsT).
constrain_GPA(_, _, MaxGroupID, GroupID, []):- GroupID #> MaxGroupID, !.
constrain_GPA(GPAs, Groups, MaxGroupID, GroupID, [DiffsH | DiffsT]):-
findall(Index, element(Index,Groups,GroupID),GroupElems),
getGPAs(GPAs, GroupElems, GroupGPAs),
minimum(MinGPA, GroupGPAs),
maximum(MaxGPA, GroupGPAs),
NextID is GroupID + 1,
DiffsH #= MaxGPA - MinGPA,
constrain_GPA(GPAs, Groups, MaxGroupID, NextID, DiffsT).
if_then_else(C, I, _):- C, !, I.
if_then_else(_, _, E):- E.
%Query to test: groups([1,2,3],[4,2,3],_,[1,2],Var).
% The results expected are:
%for groups of 1 student every combination of groups.
%For groups of 2 students the following:
%[1,2,1]
%[1,2,2]
%[2,1,1]
%[2,1,2]