/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.geppetto.common.score; import java.util.Comparator; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * ScoreKeeper is used to track/collect data with scores where the wanted result is the n best. * The 'best' is either the lowest or the highest score. It is possible to cap results over/under a given * threshold. If equal data is eligible with different score only the best score is kept thus giving room to * a different data element with worse score. (This avoids monopolizing the top scores with a single item * when using multiple scoring methods). * */ public class ScoreKeeper<T> { public static class ScoreEntry<V> { private int score; private V data; ScoreEntry(int score, V data) { this.score = score; this.data = data; } public V getData() { return data; } public int getScore() { return score; } } private class ScoreEntryComparator implements Comparator<ScoreEntry<T>> { @Override public int compare(ScoreEntry<T> a, ScoreEntry<T> b) { // for same score, artificially order on hashcode if(a.score == b.score) { if(a.getData().hashCode() == b.getData().hashCode()) return 0; return a.getData().hashCode() < b.getData().hashCode() ? -1 : 1; } return a.score < b.score ? -1 : 1; } } final private int bestScoreCount; final SortedSet<ScoreEntry<T>> scores; final private boolean higherIsBetter; final int cap; /** * Creates a score keeper that keeps at most highScoreCount scores. The highest scores * are either the highest integer value scores if higherIsBetter is true, or the lowest integer value * scores if higherIsBetter is false. * * @param bestScoreCount * @param higherIsBetter * @param cap * only values over (higherIsBetter is true), or under (higherIsBetter is false) are kept. */ public ScoreKeeper(int bestScoreCount, boolean higherIsBetter, int cap) { this.bestScoreCount = bestScoreCount; this.higherIsBetter = higherIsBetter; scores = new TreeSet<ScoreEntry<T>>(new ScoreEntryComparator()); this.cap = cap; } public boolean addScore(int score, T data) { // check cap if(higherIsBetter) { if(score < cap) return false; } else if(score > cap) return false; // System.err.printf("Add [%d] %d, %s\n", scores.size(), score, data); if(scores.size() < bestScoreCount) { ScoreEntry<T> existing = findLowest(data); // if new is better than existing, replace it, else ignore the new if(existing != null) { if(!isBetter(score, existing)) return false; scores.remove(existing); } scores.add(new ScoreEntry<T>(score, data)); return true; } ScoreEntry<T> worst = getWorst(); if(!isBetter(score, worst)) return false; ScoreEntry<T> existing = findLowest(data); // if new is better than existing, replace it, else ignore the new if(existing != null) { if(!isBetter(score, existing)) return false; scores.remove(existing); scores.add(new ScoreEntry<T>(score, data)); return true; } // add better than worst scores.remove(worst); scores.add(new ScoreEntry<T>(score, data)); return true; } private ScoreEntry<T> findLowest(T data) { for(ScoreEntry<T> e : scores) if(e.data.equals(data)) return e; return null; } public ScoreEntry<T> getBest() { if(higherIsBetter) return scores.last(); return scores.first(); } public Set<ScoreEntry<T>> getScoreEntries() { return scores; } public ScoreEntry<T> getWorst() { if(higherIsBetter) return scores.first(); return scores.last(); } private boolean isBetter(int score, ScoreEntry<T> entry) { if(entry == null) return true; // anything is better than nothing if(higherIsBetter) { if(score <= entry.score) return false; return true; } return score < entry.score; } }