ca.cutterslade.match.scheduler.MatchMaker.java Source code

Java tutorial

Introduction

Here is the source code for ca.cutterslade.match.scheduler.MatchMaker.java

Source

/*
 * 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 ca.cutterslade.match.scheduler;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;

import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * @author W.F. Hartford
 * 
 */
final class MatchMaker {

    private static final class PossibleMatchesCallable implements Callable<Set<ImmutableSet<Team>>> {

        private static final class ConstructorFunction
                implements Function<Collection<Team>, Callable<Set<ImmutableSet<Team>>>> {

            private final int teamsPerMatch;

            public ConstructorFunction(int teamsPerMatch) {
                this.teamsPerMatch = teamsPerMatch;
            }

            @Override
            public Callable<Set<ImmutableSet<Team>>> apply(Collection<Team> from) {
                return new PossibleMatchesCallable(from, teamsPerMatch);
            }
        }

        static Iterable<Callable<Set<ImmutableSet<Team>>>> forTiers(Iterable<Collection<Team>> tiers,
                int teamsPerMatch) {
            return Iterables.transform(tiers, new ConstructorFunction(teamsPerMatch));
        }

        private final Collection<Team> tier;

        private final int teamsPerMatch;

        PossibleMatchesCallable(Collection<Team> tier, int teamsPerMatch) {
            this.tier = tier;
            this.teamsPerMatch = teamsPerMatch;
        }

        @Override
        public Set<ImmutableSet<Team>> call() throws Exception {
            Set<ImmutableSet<Team>> matches = Sets.newLinkedHashSet();
            build(Sets.<Team>newLinkedHashSet(), matches);
            return matches;
        }

        private void build(Set<Team> match, Set<ImmutableSet<Team>> matches) {
            Iterable<Team> options = match.isEmpty() ? this.tier
                    : Iterables.filter(this.tier, Predicates.not(Predicates.in(match)));
            for (Team t : options) {
                Set<Team> thisMatch = Sets.newLinkedHashSet(match);
                thisMatch.add(t);
                if (thisMatch.size() == teamsPerMatch)
                    matches.add(ImmutableSet.copyOf(thisMatch));
                else
                    build(thisMatch, matches);
            }
        }
    }

    private static class SadFacesCallable implements Callable<Integer> {

        private final SadFaceFactor factor;

        private final int weight;

        private final Slot slot;

        private final ImmutableSet<Team> match;

        private final Iterable<Match> existingMatches;

        private final int limit;

        SadFacesCallable(SadFaceFactor factor, int weight, Slot slot, ImmutableSet<Team> match,
                Iterable<Match> existingMatches, int limit) {
            this.factor = factor;
            this.weight = weight;
            this.slot = slot;
            this.match = match;
            this.existingMatches = existingMatches;
            this.limit = limit;
        }

        @Override
        public Integer call() throws Exception {
            return weight * factor.getSadFaces(slot, match, existingMatches, limit);
        }

    }

    private static final Random RANDOM = new Random();

    private final Executor executor = new Executor();

    private final Configuration configuration;

    private final ImmutableSet<Team> teams;

    private final ImmutableMultimap<Tier, Team> tiers;

    private final ImmutableMultimap<Day, Slot> days;

    private final int teamSize;

    MatchMaker(Configuration configuration, ImmutableSet<Slot> slots, ImmutableSet<Team> teams, int teamSize) {
        if (null == configuration)
            throw new IllegalArgumentException("configuration may not be null");
        if (null == slots)
            throw new IllegalArgumentException("slots may not be null");
        if (null == teams)
            throw new IllegalArgumentException("teams may not be null");
        if (2 > teamSize)
            throw new IllegalArgumentException("size must be two or greater");
        this.configuration = configuration;
        this.teams = teams;
        ImmutableMultimap.Builder<Tier, Team> tiers = ImmutableMultimap.builder();
        for (Team t : teams)
            tiers.put(t.getTier(), t);
        this.tiers = tiers.build();

        ImmutableMultimap.Builder<Day, Slot> days = ImmutableMultimap.builder();
        for (Slot s : slots)
            days.put(s.getDay(), s);
        this.days = days.build();
        this.teamSize = teamSize;
    }

    ImmutableSet<Match> getMatches() throws InterruptedException {
        ImmutableSet<ImmutableSet<Team>> matches = executor
                .interleaf(PossibleMatchesCallable.forTiers(this.tiers.asMap().values(), teamSize));
        ImmutableSet.Builder<Match> b = ImmutableSet.builder();
        final Collection<Day> days;
        if (configuration.isRandomizeDayOrder()) {
            List<Day> d = Lists.newArrayList(this.days.keySet());
            Collections.shuffle(d);
            days = d;
        } else
            days = this.days.keySet();
        for (Day d : days)
            b.addAll(getMatchesForDay(d, b.build(), matches));
        return b.build();
    }

    private Iterable<Match> getMatchesForDay(Day day, Iterable<Match> existing,
            ImmutableSet<ImmutableSet<Team>> matches) throws InterruptedException {
        Set<Team> teams = Sets.newHashSet(this.teams);
        Set<Match> made = Sets.newHashSet();
        while (!teams.isEmpty()) {
            for (Slot s : getDaySlots(day)) {
                final ImmutableSet<Team> bestMatch = getBestMatch(Iterables.concat(existing, made), matches, teams,
                        s);
                made.add(makeMatch(s, bestMatch));
                teams.removeAll(bestMatch);
            }
        }
        return ImmutableSet.copyOf(made);
    }

    private Collection<Slot> getDaySlots(Day day) {
        Collection<Slot> slots = this.days.get(day);
        if (configuration.isRandomizeSlotOrder()) {
            List<Slot> s = Lists.newArrayList(slots);
            Collections.shuffle(s, RANDOM);
            slots = s;
        }
        return slots;
    }

    private ImmutableSet<Team> getBestMatch(Iterable<Match> existing, ImmutableSet<ImmutableSet<Team>> matches,
            Set<Team> teams, Slot s) throws InterruptedException {
        int leastSadFaces = Integer.MAX_VALUE;
        ImmutableSet<Team> bestMatch = null;
        for (ImmutableSet<Team> m : matches) {
            if (teams.containsAll(m)) {
                int sadFaces = getSadFaces(s, m, existing, leastSadFaces);
                if (sadFaces < leastSadFaces) {
                    bestMatch = m;
                    if (0 == sadFaces)
                        break;
                    leastSadFaces = sadFaces;
                }
            }
        }
        if (null == bestMatch)
            throw new AssertionError("bestMatch is null");
        return bestMatch;
    }

    private int getSadFaces(Slot slot, ImmutableSet<Team> match, Iterable<Match> existingMatches, int limit)
            throws InterruptedException {
        List<SadFacesCallable> sfcs = Lists.newArrayList();
        for (SadFaceFactor f : SadFaceFactor.values())
            sfcs.add(new SadFacesCallable(f, configuration.getFactor(f), slot, match, existingMatches, limit));
        return executor.sum(sfcs);
    }

    private Match makeMatch(Slot s, final ImmutableSet<Team> bestMatch) {
        final Match m;
        if (configuration.isRandomizeMatchOrder()) {
            List<Team> r = Lists.newArrayList(bestMatch);
            Collections.shuffle(r, RANDOM);
            m = new Match(r, s);
        } else
            m = new Match(bestMatch, s);
        return m;
    }

}