diff --git a/src/main/java/se/su/dsv/scipro/match/GreedyMatchingAlgorithm.java b/src/main/java/se/su/dsv/scipro/match/GreedyMatchingAlgorithm.java index 860aba6767..fd12006d0a 100644 --- a/src/main/java/se/su/dsv/scipro/match/GreedyMatchingAlgorithm.java +++ b/src/main/java/se/su/dsv/scipro/match/GreedyMatchingAlgorithm.java @@ -22,143 +22,83 @@ public class GreedyMatchingAlgorithm implements MatchingAlgorithm { private Weights weights; @Override - public Result match(List<Availability> supervisorAvailability, - List<ProjectIdea> unmatchedProjectIdeas, Weights weights) { - List<Match> matchList = new ArrayList<Match>(); - - this.weights = weights; + public Result match(List<Availability> supervisorAvailability, List<ProjectIdea> unmatchedProjectIdeas, Weights weights) { + this.weights = weights; + List<Pair> pairList = new ArrayList<Pair>(); + List<Match> matchList = new ArrayList<Match>(); - List<String> availableProjectClasses = getAvailableProjectClass(unmatchedProjectIdeas, supervisorAvailability); - - HashMap<String, List<ProjectIdea>> sortedProjectIdeas = getSortedProjectIdeas(availableProjectClasses, unmatchedProjectIdeas); - HashMap<String, List<Availability>> sortedAvailability = getSortedAvailability(availableProjectClasses, supervisorAvailability); - logger.info("SortedAvail.size: " + sortedAvailability.size()); - logger.info("sortedProjIdeas.size: " + sortedProjectIdeas.size()); - for (String projectClassCode : availableProjectClasses) { - logger.info("Checking class:" + projectClassCode); - matchList.addAll(matchProjectIdeas(sortedProjectIdeas.get(projectClassCode), sortedAvailability.get(projectClassCode))); - } - logger.info("Matchlist.size: " + matchList.size()); + while(!unmatchedProjectIdeas.isEmpty()) { + pairList.clear(); + matchProjectIdeas(unmatchedProjectIdeas, supervisorAvailability, pairList); + Collections.sort(pairList); + if(!pairList.isEmpty()) { + Pair foundPair = pairList.get(0); + for(Availability availability : supervisorAvailability) { + if(availability.getSupervisor().equals(foundPair.getMatch().getSupervisor())) { + if(availability.getNumCapable() > availability.getNumMatched()) { + availability.setNumMatched(availability.getNumMatched() + 1); + matchList.add(foundPair.getMatch()); + } + } + } - // If there are unmatched bachelor theses and unmatched master supervisors match them - List<ProjectIdea> projectIdeaList = sortedProjectIdeas.get(ProjectClass.BACHELOR); - List<Availability> availabilityList = sortedAvailability.get(ProjectClass.MASTER); - - if (projectIdeaList != null && availabilityList != null) { - if (!projectIdeaList.isEmpty() - && !availabilityList.isEmpty()) { - matchList.addAll(matchProjectIdeas(projectIdeaList, availabilityList)); - } - } - unmatchedProjectIdeas = getUnmatchedProjectIdeas(matchList, unmatchedProjectIdeas); - return new Result(matchList, unmatchedProjectIdeas); - } - - /** - * Take all ProjectIdeas and remove those who have been matched - * @param matchList the list - * @param unmatchedProjectIdeas unmatched ideas - * @return List<ProjectIdea> - */ - private List<ProjectIdea> getUnmatchedProjectIdeas(List<Match> matchList, - List<ProjectIdea> unmatchedProjectIdeas) { - for (Match match : matchList) { - ProjectIdea matchedProjectIdea = match.getProjectIdea(); - Iterator<ProjectIdea> projectIdeaIterator = unmatchedProjectIdeas.iterator(); - while (projectIdeaIterator.hasNext()) { - ProjectIdea unmatchedProjectIdea = projectIdeaIterator.next(); - if (matchedProjectIdea.equals(unmatchedProjectIdea)) { - projectIdeaIterator.remove(); - } - } - } - return unmatchedProjectIdeas; + Iterator<ProjectIdea> projectIdeaIterator = unmatchedProjectIdeas.iterator(); + while(projectIdeaIterator.hasNext()) { + ProjectIdea projectIdea = projectIdeaIterator.next(); + if(projectIdea.equals(foundPair.getMatch().getProjectIdea())) { + projectIdeaIterator.remove(); + } + } + } else { + break; + } + } + return new Result(matchList, unmatchedProjectIdeas); } /** * Match project ideas with supervisors (based on availability) and return the best match. * @param projectIdeaList project ideas that should be matched * @param availabilityList supervisors that should be matched - * @return List<Match> the best matches are returned - */ - private List<Match> matchProjectIdeas(List<ProjectIdea> projectIdeaList, List<Availability> availabilityList) { - List<Match> matchList = new ArrayList<Match>(); - - Iterator<ProjectIdea> projectIdeaIterator = projectIdeaList.iterator(); - while (projectIdeaIterator.hasNext()) { - ProjectIdea projectIdea = projectIdeaIterator.next(); - logger.info("Matching project idea:" + projectIdea); - Pair matchPair = null; - Iterator<Availability> availabilityIterator = availabilityList.iterator(); - while (availabilityIterator.hasNext()) { - Availability availability = availabilityIterator.next(); + * @param pairList the set + */ + private void matchProjectIdeas(List<ProjectIdea> projectIdeaList, List<Availability> availabilityList, List<Pair> pairList) { + for (ProjectIdea projectIdea : projectIdeaList) { + logger.info("Matching project idea:" + projectIdea); + for(Availability availability : availabilityList) { logger.info("Matching project idea with supervisor:" + availability.getSupervisor()); - if (!availability.isAvailable()) { - availabilityIterator.remove(); logger.info("Not an available supervisor"); continue; } + + if(availability.getProjectClass().toString().equalsIgnoreCase(ProjectClass.BACHELOR) && + projectIdea.getProjectClass().toString().equalsIgnoreCase(ProjectClass.MASTER)) { + logger.info("A bachelor supervisor cannot handle master ideas."); + continue; + } if (!isPossibleSupervisor(projectIdea, availability.getSupervisor())) { logger.info("Not possible supervisor"); continue; } - - if(matchPair == null){ - Match match = new Match(); // create a new match - match.setSupervisor(availability.getSupervisor()); - match.setProjectIdea(projectIdea); - matchPair = new Pair(match, new Availability(null, 1L, 1, new ProjectClass())); - } - - matchPair = getBestMatch(matchPair, availability); + pairList.add(getPair(projectIdea, availability)); } - if (matchPair != null && matchPair.getMatch().getPoints() != -1) { - matchList.add(matchPair.getMatch()); - projectIdeaIterator.remove(); - } } - - return matchList; } /** * Return the best match, the old one or the new one with supervisor - * @param oldMatchPair the match pair + * @param projectIdea projectIdea * @param availability the availability * @return Pair */ - private Pair getBestMatch(final Pair oldMatchPair, Availability availability) { - Match oldMatch = oldMatchPair.getMatch(); - Availability oldAvailability = oldMatchPair.getAvailability(); - + private Pair getPair(ProjectIdea projectIdea, Availability availability) { Match match = new Match(); - match.setProjectIdea(oldMatch.getProjectIdea()); + match.setProjectIdea(projectIdea); match.setSupervisor(availability.getSupervisor()); - match = calculateScore(match); - - if (match.getPoints() > oldMatch.getPoints()) { - - availability.setNumMatched(availability.getNumMatched() + 1); - oldAvailability.setNumMatched(oldAvailability.getNumMatched() - 1); - return new Pair(match, availability); - - } else if (match.getPoints() == oldMatch.getPoints()) { - - if (availability.getAvailability() > oldAvailability.getAvailability()) { - - availability.setNumMatched(availability.getNumMatched() + 1); - oldAvailability.setNumMatched(oldAvailability.getNumMatched() - 1); - return new Pair(match, availability); - - } else { - return oldMatchPair; - } - } else { - return oldMatchPair; - } + return new Pair(match, availability); } /** @@ -192,9 +132,7 @@ public class GreedyMatchingAlgorithm implements MatchingAlgorithm { */ private boolean isNotRejectedBySupervisor(ProjectIdea projectIdea, Employee supervisor) { for (Match oldMatch : projectIdea.getMatchHistory()) { - if ( - //oldMatch.getSupervisor() != null && oldMatch.getSupervisor().equals(supervisor) && we are not interested in who is the supervisor, we are interested in who is rejectedBy - oldMatch.getRejectedBy() != null && oldMatch.getRejectedBy().equals(supervisor.getUser()) && + if (oldMatch.getRejectedBy() != null && oldMatch.getRejectedBy().equals(supervisor.getUser()) && oldMatch.getStatus() != null && oldMatch.getStatus().equals(Match.Status.REJECTED)) { return false; } @@ -252,91 +190,13 @@ public class GreedyMatchingAlgorithm implements MatchingAlgorithm { match.setPoints(points); return match; } - - /** - * - * @param availableProjectClasses the list with available project classes - * @param supervisorAvailability the list with available supervisors - * @return Supervisors availability sorted on ProjectClasses.code - */ - private HashMap<String, List<Availability>> getSortedAvailability( - List<String> availableProjectClasses, - List<Availability> supervisorAvailability) { - HashMap<String, List<Availability>> sortedAvailability = new HashMap<String, List<Availability>>(); - for (String projectClassCode : availableProjectClasses) { - sortedAvailability.put(projectClassCode, new ArrayList<Availability>()); - } - - for (Availability availability : supervisorAvailability) { - sortedAvailability.get(availability.getProjectClass().getCode()).add(availability); - } - - //start Tom+Anton test - //List<Availability> bachelorAvailabilities = sortedAvailability.get(ProjectClass.BACHELOR) != null ? - // sortedAvailability.get(ProjectClass.BACHELOR) : new ArrayList<Availability>(); - List<Availability> bachelorAvailabilities = sortedAvailability.get(ProjectClass.BACHELOR); - if (bachelorAvailabilities != null) - for (Availability av : supervisorAvailability) { - if(!bachelorAvailabilities.contains(av)) - bachelorAvailabilities.add(av); - } - //sortedAvailability.put(ProjectClass.BACHELOR, bachelorAvailabilities); - //end T+A - - return sortedAvailability; - } - - /** - * @param availableProjectClasses the project classes that exists, regardless of there is a match or not, - * that is ok because the List with project ideas will be empty in case no project class match - * @param unmatchedProjectIdeaList the unmatched project ideas - * @return unmatched ProjectIdeas sorted on ProjectClasses.code - */ - private HashMap<String, List<ProjectIdea>> getSortedProjectIdeas( - List<String> availableProjectClasses, - List<ProjectIdea> unmatchedProjectIdeaList) { - HashMap<String, List<ProjectIdea>> sortedProjectIdeas = new HashMap<String, List<ProjectIdea>>(); - - for (String projectClassCode : availableProjectClasses) { - sortedProjectIdeas.put(projectClassCode, new ArrayList<ProjectIdea>()); - } - - for (ProjectIdea projectIdea : unmatchedProjectIdeaList) { - sortedProjectIdeas.get(projectIdea.getProjectClass().getCode()).add(projectIdea); - } - - return sortedProjectIdeas; - } - - /** - * - * @param unmatchedProjectIdeas a list of unmatched project ideas - * @param supervisorAvailability list of available supervisors - * @return all available ProjectClass codes - */ - private List<String> getAvailableProjectClass( - List<ProjectIdea> unmatchedProjectIdeas, List<Availability> supervisorAvailability) { - List<String> availableProjectClasses = new ArrayList<String>(); - for (ProjectIdea projectIdea : unmatchedProjectIdeas) { - if (!availableProjectClasses.contains(projectIdea.getProjectClass().getCode())) { - availableProjectClasses.add(projectIdea.getProjectClass().getCode()); - } - } - - for (Availability availability : supervisorAvailability) { - if (!availableProjectClasses.contains(availability.getProjectClass().getCode())) { - availableProjectClasses.add(availability.getProjectClass().getCode()); - } - } - return availableProjectClasses; - } - - private class Pair { + private class Pair implements Comparable<Pair> { private Match match; private Availability availability; - public Pair(Match match, Availability availability) { + public Pair(Match match, Availability availability + ) { this.match = match; this.availability = availability; } @@ -348,5 +208,17 @@ public class GreedyMatchingAlgorithm implements MatchingAlgorithm { public Availability getAvailability() { return availability; } + + @Override + public int compareTo(Pair otherPair) { + if(match.getPoints() > otherPair.getMatch().getPoints()) { + return -1; + } else if(otherPair.getMatch().getPoints() > match.getPoints()) { + return 1; + } else { + return availability.compareTo(otherPair.getAvailability()); + } + } + } } \ No newline at end of file diff --git a/src/main/java/se/su/dsv/scipro/match/dataobject/Availability.java b/src/main/java/se/su/dsv/scipro/match/dataobject/Availability.java index ab71782723..eef7bc514c 100644 --- a/src/main/java/se/su/dsv/scipro/match/dataobject/Availability.java +++ b/src/main/java/se/su/dsv/scipro/match/dataobject/Availability.java @@ -8,7 +8,7 @@ import se.su.dsv.scipro.data.dataobjects.ProjectClass; /** * A class that specifies how available a supervisor is(in terms of thesis supervision) */ -public class Availability implements Serializable{ +public class Availability implements Serializable, Comparable<Availability> { private static final long serialVersionUID = 1L; @@ -53,6 +53,17 @@ public class Availability implements Serializable{ public void setNumMatched(Long num) { numMatched = num; } + + @Override + public int compareTo(Availability availability) { + if(getAvailability() > availability.getAvailability()) { + return -1; + } else if(availability.getAvailability() > getAvailability()) { + return 1; + } else { + return 0; + } + } @Override public String toString() { diff --git a/src/main/java/se/su/dsv/scipro/match/dataobject/Match.java b/src/main/java/se/su/dsv/scipro/match/dataobject/Match.java index 267a673158..698aba58a2 100644 --- a/src/main/java/se/su/dsv/scipro/match/dataobject/Match.java +++ b/src/main/java/se/su/dsv/scipro/match/dataobject/Match.java @@ -21,7 +21,7 @@ import se.su.dsv.scipro.data.dataobjects.User; @Cacheable(true) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name="matchings") -public class Match extends DomainObject { +public class Match extends DomainObject implements Comparable<Match> { public static enum Type { PRE_APPROVED, @@ -169,6 +169,18 @@ public class Match extends DomainObject { ", dateCreated=" + (getDateCreated() != null ? getDateCreated() :"") + "]"; } + @Override + public int compareTo(Match otherMatch) { + if(points > otherMatch.getPoints()) { + return -1; + } else if(otherMatch.getPoints() > points) { + return 1; + } else { + return 0; + } + } + + @Override public int hashCode() { final int prime = 31; diff --git a/src/test/java/se/su/dsv/scipro/match/TestGreedyMatchingAlgorithm.java b/src/test/java/se/su/dsv/scipro/match/TestGreedyMatchingAlgorithm.java index 4cd145cb33..0c177e8875 100644 --- a/src/test/java/se/su/dsv/scipro/match/TestGreedyMatchingAlgorithm.java +++ b/src/test/java/se/su/dsv/scipro/match/TestGreedyMatchingAlgorithm.java @@ -500,6 +500,7 @@ public class TestGreedyMatchingAlgorithm { Employee bachelorSupervisor2 = createSupervisor("David", "Hallberg", languages); supervisorAvailability.add(new Availability(masterSupervisor, 0L, 1, bachelorProjectClass)); supervisorAvailability.add(new Availability(bachelorSupervisor2, 0L, 1, bachelorProjectClass)); + addKeyWords(bachelorSupervisor2, bachelorProjectIdea, createKeyword(keywordTypeWord, "Design", false)); unmatchedProjectIdeas.add(bachelorProjectIdea); Result result = new GreedyMatchingAlgorithm().match(supervisorAvailability, unmatchedProjectIdeas, weights); assertTrue(result.matches.size() > 0); @@ -560,7 +561,6 @@ public class TestGreedyMatchingAlgorithm { @Rollback /* test that a master which has filled up his slot for bachelor but has slots left for master can supervise a bachelor idea */ public void testIncreaseSlotForMasterSupervisor_v2() { - /* Employee bachelorSupervisor2 = createSupervisor("David", "Hallberg", languages); supervisorAvailability.add(new Availability(masterSupervisor, 3L, 3, bachelorProjectClass)); supervisorAvailability.add(new Availability(masterSupervisor, 3L, 4, masterProjectClass)); @@ -571,11 +571,29 @@ public class TestGreedyMatchingAlgorithm { masterProjectIdea.setPreferredSupervisor(masterSupervisor); addKeyWords(masterSupervisor, masterProjectIdea, createKeyword(keywordTypeWord, "UML", false)); Result result = new GreedyMatchingAlgorithm().match(supervisorAvailability, unmatchedProjectIdeas, weights); - assertTrue(result.matches.size() > 0); - assertTrue(result.matches.get(0).getProjectIdea().equals(masterProjectIdea)); - assertTrue(result.matches.get(0).getSupervisor().equals(masterSupervisor)); + assertTrue(result.matches.size() > 0); + boolean foundMasterProjIdea = false; + boolean foundMasterSuperVisor= false; + + boolean foundBachelorProjIdea = false; + boolean foundBachelorSuperVisor= false; + + for(Match match : result.matches) { + if(match.getProjectIdea().equals(masterProjectIdea) && match.getSupervisor().equals(masterSupervisor)) { + foundMasterProjIdea = true; + foundMasterSuperVisor = true; + } + + if(match.getProjectIdea().equals(bachelorProjectIdea) && match.getSupervisor().equals(bachelorSupervisor2)) { + foundBachelorProjIdea = true; + foundBachelorSuperVisor = true; + } + } + assertTrue(foundMasterProjIdea); + assertTrue(foundBachelorProjIdea); + assertTrue(foundBachelorSuperVisor); + assertTrue(foundMasterSuperVisor); assertTrue(result.unmatched.size() == 0); - */ } }