/* * Copyright 2010 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.optaplanner.examples.travelingtournament.solver.move.factory; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.optaplanner.core.impl.heuristic.move.Move; import org.optaplanner.core.impl.heuristic.selector.move.factory.MoveListFactory; import org.optaplanner.examples.travelingtournament.domain.Day; import org.optaplanner.examples.travelingtournament.domain.Match; import org.optaplanner.examples.travelingtournament.domain.Team; import org.optaplanner.examples.travelingtournament.domain.TravelingTournament; import org.optaplanner.examples.travelingtournament.solver.move.MatchChainRotationsMove; public class MatchChainRotationsMoveFactory implements MoveListFactory<TravelingTournament> { @Override public List<Move<TravelingTournament>> createMoveList(TravelingTournament travelingTournament) { List<Move<TravelingTournament>> moveList = new ArrayList<>(); RotationMovesFactory rotationMovesFactory = new RotationMovesFactory(travelingTournament); rotationMovesFactory.addDayRotation(moveList); rotationMovesFactory.addTeamRotation(moveList); return moveList; } private static class RotationMovesFactory { private List<Day> dayList; private List<Team> teamList; private List<Match> matchList; private Map<Day, Map<Team, Match>> dayTeamMap; private Map<Team, Map<Day, Match>> teamDayMap; private Map<Team, Map<Team, Match>> homeTeamAwayTeamMap; public RotationMovesFactory(TravelingTournament travelingTournament) { dayList = travelingTournament.getDayList(); teamList = travelingTournament.getTeamList(); matchList = travelingTournament.getMatchList(); createMaps(); } private void createMaps() { dayTeamMap = new HashMap<>(dayList.size()); for (Day day : dayList) { // This map should be ordered so the order of the matchRotationList is the same (when it's used as tabu) dayTeamMap.put(day, new LinkedHashMap<>(teamList.size())); } teamDayMap = new HashMap<>(teamList.size()); homeTeamAwayTeamMap = new HashMap<>(teamList.size()); for (Team team : teamList) { // This map should be ordered so the order of the matchRotationList is the same (when it's used as tabu) teamDayMap.put(team, new LinkedHashMap<>(dayList.size())); homeTeamAwayTeamMap.put(team, new LinkedHashMap<>(teamList.size() - 1)); } for (Match match : matchList) { Map<Team, Match> subTeamMap = dayTeamMap.get(match.getDay()); subTeamMap.put(match.getHomeTeam(), match); subTeamMap.put(match.getAwayTeam(), match); teamDayMap.get(match.getHomeTeam()).put(match.getDay(), match); teamDayMap.get(match.getAwayTeam()).put(match.getDay(), match); homeTeamAwayTeamMap.get(match.getHomeTeam()).put(match.getAwayTeam(), match); } } private Team getOtherTeam(Match match, Team team) { return match.getHomeTeam().equals(team) ? match.getAwayTeam() : match.getHomeTeam(); } /** * @TODO clean up this code */ private void addDayRotation(List<Move<TravelingTournament>> moveList) { for (ListIterator<Day> firstDayIt = dayList.listIterator(); firstDayIt.hasNext();) { Day firstDay = firstDayIt.next(); Map<Team, Match> firstDayTeamMap = dayTeamMap.get(firstDay); for (ListIterator<Day> secondDayIt = dayList.listIterator(firstDayIt.nextIndex()); secondDayIt.hasNext();) { Day secondDay = secondDayIt.next(); List<Match> clonedFirstDayMatchList = new ArrayList<>(firstDayTeamMap.values()); while (!clonedFirstDayMatchList.isEmpty()) { List<Match> rotateList = new ArrayList<>(4); Match startMatch = clonedFirstDayMatchList.remove(0); boolean otherInFirst = false; rotateList.add(startMatch); Team startHomeTeam = startMatch.getHomeTeam(); Team nextTeamToFind = startMatch.getAwayTeam(); while (!startHomeTeam.equals(nextTeamToFind)) { Map<Team, Match> subTeamMap = dayTeamMap.get(otherInFirst ? firstDay : secondDay); Match repairMatch = subTeamMap.get(nextTeamToFind); if (otherInFirst) { clonedFirstDayMatchList.remove(repairMatch); } rotateList.add(repairMatch); nextTeamToFind = getOtherTeam(repairMatch, nextTeamToFind); otherInFirst = !otherInFirst; } // assert(rotateList.size() % 2 == 0); // if size is 2 then addCachedHomeAwaySwapMoves will have done it if (rotateList.size() > 2) { List<Match> emptyList = Collections.emptyList(); MatchChainRotationsMove rotateMove = new MatchChainRotationsMove(rotateList, emptyList); moveList.add(rotateMove); } } } } } /** * @TODO clean up this code */ private void addTeamRotation(List<Move<TravelingTournament>> moveList) { for (ListIterator<Team> firstTeamIt = teamList.listIterator(); firstTeamIt.hasNext();) { Team firstTeam = firstTeamIt.next(); Map<Day, Match> firstTeamDayMap = teamDayMap.get(firstTeam); for (ListIterator<Team> secondTeamIt = teamList.listIterator(firstTeamIt.nextIndex()); secondTeamIt.hasNext();) { Team secondTeam = secondTeamIt.next(); List<Match> clonedFirstTeamMatchList = new ArrayList<>(firstTeamDayMap.values()); while (!clonedFirstTeamMatchList.isEmpty()) { List<Match> firstRotateList = new ArrayList<>(); List<Match> secondRotateList = new ArrayList<>(); Match firstStartMatch = clonedFirstTeamMatchList.remove(0); Team firstStartTeam = getOtherTeam(firstStartMatch, firstTeam); Day startDay = firstStartMatch.getDay(); boolean firstTeamIsHomeTeam = firstStartMatch.getHomeTeam().equals(firstTeam); Match secondStartMatch = teamDayMap.get(secondTeam).get(startDay); if (firstStartMatch.equals(secondStartMatch)) { break; } firstRotateList.add(0, firstStartMatch); secondRotateList.add(secondStartMatch); Map<Team, Match> visitedTeamMap = new HashMap<>(); Team teamToFind = getOtherTeam(secondStartMatch, secondTeam); while (!teamToFind.equals(firstStartTeam)) { // boolean shortcut = visitedTeamMap.containsKey(teamToFind); // if (shortcut) { Match firstRepairMatch = homeTeamAwayTeamMap .get(firstTeamIsHomeTeam ? firstTeam : teamToFind) .get(firstTeamIsHomeTeam ? teamToFind : firstTeam); if (!clonedFirstTeamMatchList.contains(firstRepairMatch)) { if (visitedTeamMap.containsKey(teamToFind)) { // shortcut splitoff is possible Match shortcutMatch = visitedTeamMap.get(teamToFind); int shortcutSize = firstRotateList.indexOf(shortcutMatch) + 1; int reverseShortcutSize = firstRotateList.size() - shortcutSize; List<Match> firstShortcutRotateList = new ArrayList<>( firstRotateList.subList(0, shortcutSize)); for (Match match : firstShortcutRotateList) { visitedTeamMap.remove(getOtherTeam(match, firstTeam)); } List<Match> secondShortcutRotateList = new ArrayList<>( secondRotateList.subList(reverseShortcutSize, secondRotateList.size())); firstRotateList = new ArrayList<>( firstRotateList.subList(shortcutSize, firstRotateList.size())); secondRotateList = new ArrayList<>( secondRotateList.subList(0, reverseShortcutSize)); addTeamRotateMove(moveList, firstShortcutRotateList, secondShortcutRotateList); } firstTeamIsHomeTeam = !firstTeamIsHomeTeam; // Team firstRepairHomeTeam = (firstTeamIsHomeTeam ^ shortcut) ? firstTeam : teamToFind; // Team firstRepairAwayTeam = (firstTeamIsHomeTeam ^ shortcut) ? teamToFind : firstTeam; // Match firstRepairMatch = homeTeamAwayTeamMap // .get(firstRepairHomeTeam).get(firstRepairAwayTeam); firstRepairMatch = homeTeamAwayTeamMap .get(firstTeamIsHomeTeam ? firstTeam : teamToFind) .get(firstTeamIsHomeTeam ? teamToFind : firstTeam); } Day repairDay = firstRepairMatch.getDay(); Match secondRepairMatch = teamDayMap.get(secondTeam).get(repairDay); clonedFirstTeamMatchList.remove(firstRepairMatch); visitedTeamMap.put(teamToFind, firstRepairMatch); firstRotateList.add(0, firstRepairMatch); secondRotateList.add(secondRepairMatch); teamToFind = getOtherTeam(secondRepairMatch, secondTeam); } addTeamRotateMove(moveList, firstRotateList, secondRotateList); } } } } private void addTeamRotateMove(List<Move<TravelingTournament>> moveList, List<Match> firstRotateList, List<Match> secondRotateList) { assert (firstRotateList.size() == secondRotateList.size()); // if size is 1 then addCachedHomeAwaySwapMoves will have done it // if size is 2 then addDayRotation will have done it by 1 list of size 4 if (firstRotateList.size() > 2) { MatchChainRotationsMove rotateMove = new MatchChainRotationsMove(firstRotateList, secondRotateList); moveList.add(rotateMove); } } } }