/**
* ScoreMap
* Copyright 2010 by Michael Peter Christen, mc@yacy.net, Frankfurt am Main, Germany
* First released 14.10.2010 at http://yacy.net
*
* $LastChangedDate$
* $LastChangedRevision$
* $LastChangedBy$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.cora.sorting;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import net.yacy.cora.util.StringBuilderComparator;
public class OrderedScoreMap<E> extends AbstractScoreMap<E> implements ScoreMap<E> {
protected final Map<E, AtomicInteger> map; // a mapping from a reference to the cluster key
public OrderedScoreMap(final Comparator<? super E> comparator) {
if (comparator == null) {
this.map = new HashMap<E, AtomicInteger>();
} else {
this.map = new TreeMap<E, AtomicInteger>(comparator);
}
}
@Override
public Iterator<E> iterator() {
return this.map.keySet().iterator();
}
@Override
public synchronized void clear() {
this.map.clear();
}
/**
* shrink the cluster to a demanded size
* @param maxsize
*/
@Override
public void shrinkToMaxSize(final int maxsize) {
if (this.map.size() <= maxsize) return;
int minScore = getMinScore();
while (this.map.size() > maxsize) {
minScore++;
shrinkToMinScore(minScore);
}
}
/**
* shrink the cluster in such a way that the smallest score is equal or greater than a given minScore
* @param minScore
*/
@Override
public void shrinkToMinScore(final int minScore) {
synchronized (this.map) {
final Iterator<Map.Entry<E, AtomicInteger>> i = this.map.entrySet().iterator();
Map.Entry<E, AtomicInteger> entry;
while (i.hasNext()) {
entry = i.next();
if (entry.getValue().intValue() < minScore) i.remove();
}
}
}
@Override
public int size() {
synchronized (this.map) {
return this.map.size();
}
}
/**
* return true if the size of the score map is smaller then the given size
* @param size
* @return
*/
@Override
public boolean sizeSmaller(final int size) {
if (this.map.size() < size) return true;
synchronized (this.map) {
return this.map.size() < size;
}
}
@Override
public boolean isEmpty() {
if (this.map.isEmpty()) return true;
synchronized (this.map) {
return this.map.isEmpty();
}
}
@Override
public void inc(final E obj) {
if (obj == null) return;
AtomicInteger score = this.map.get(obj);
if (score != null) {
score.incrementAndGet();
return;
}
synchronized (this.map) {
score = this.map.get(obj);
if (score == null) {
this.map.put(obj, new AtomicInteger(1));
return;
}
}
score.incrementAndGet();
}
@Override
public void dec(final E obj) {
if (obj == null) return;
AtomicInteger score;
synchronized (this.map) {
score = this.map.get(obj);
if (score == null) {
this.map.put(obj, new AtomicInteger(-1));
return;
}
}
score.decrementAndGet();
}
@Override
public void set(final E obj, final int newScore) {
if (obj == null) return;
AtomicInteger score;
synchronized (this.map) {
score = this.map.get(obj);
if (score == null) {
this.map.put(obj, new AtomicInteger(newScore));
return;
}
}
score.getAndSet(newScore);
}
@Override
public void inc(final E obj, final int incrementScore) {
if (obj == null) return;
AtomicInteger score;
synchronized (this.map) {
score = this.map.get(obj);
if (score == null) {
this.map.put(obj, new AtomicInteger(incrementScore));
return;
}
}
score.addAndGet(incrementScore);
}
@Override
public void dec(final E obj, final int incrementScore) {
inc(obj, -incrementScore);
}
@Override
public int delete(final E obj) {
// deletes entry and returns previous score
if (obj == null) return 0;
final AtomicInteger score;
synchronized (this.map) {
score = this.map.remove(obj);
if (score == null) return 0;
}
return score.intValue();
}
@Override
public boolean containsKey(final E obj) {
synchronized (this.map) {
return this.map.containsKey(obj);
}
}
@Override
public int get(final E obj) {
if (obj == null) return 0;
final AtomicInteger score;
synchronized (this.map) {
score = this.map.get(obj);
}
if (score == null) return 0;
return score.intValue();
}
public SortedMap<E, AtomicInteger> tailMap(final E obj) {
if (this.map instanceof TreeMap) {
return ((TreeMap<E, AtomicInteger>) this.map).tailMap(obj);
}
throw new UnsupportedOperationException("map must have comparator");
}
private int getMinScore() {
if (this.map.isEmpty()) return -1;
int minScore = Integer.MAX_VALUE;
synchronized (this.map) {
for (final Map.Entry<E, AtomicInteger> entry: this.map.entrySet()) if (entry.getValue().intValue() < minScore) {
minScore = entry.getValue().intValue();
}
}
return minScore;
}
/**
* @return largest score value
*/
public int getMaxScore() {
if (this.map.isEmpty()) {
return -1;
}
int maxScore = Integer.MIN_VALUE;
for (final Map.Entry<E, AtomicInteger> entry : this.map.entrySet()) {
if (entry.getValue().intValue() > maxScore) {
maxScore = entry.getValue().intValue();
}
}
return maxScore;
}
@Override
public Iterator<E> keys(final boolean up) {
synchronized (this.map) {
// re-organize entries
final TreeMap<Integer, Set<E>> m = new TreeMap<Integer, Set<E>>();
Set<E> s;
for (final Map.Entry<E, AtomicInteger> entry: this.map.entrySet()) {
s = m.get(entry.getValue().intValue());
if (s == null) {
s = this.map instanceof TreeMap ? new TreeSet<E>(((TreeMap<E, AtomicInteger>) this.map).comparator()) : new HashSet<E>();
s.add(entry.getKey());
m.put(entry.getValue().intValue(), s);
} else {
s.add(entry.getKey());
}
}
// flatten result
final List<E> l = new ArrayList<E>(this.map.size());
for (final Set<E> f: m.values()) {
for (final E e: f) l.add(e);
}
if (up) return l.iterator();
// optionally reverse list
final List<E> r = new ArrayList<E>(l.size());
for (int i = l.size() - 1; i >= 0; i--) r.add(l.get(i));
return r.iterator();
}
}
public static void main(String[] args) {
OrderedScoreMap<StringBuilder> w = new OrderedScoreMap<StringBuilder>(StringBuilderComparator.CASE_INSENSITIVE_ORDER);
Random r = new Random();
for (int i = 0; i < 10000; i++) {
w.inc(new StringBuilder("a" + ((char) (('a') + r.nextInt(26)))));
}
for (StringBuilder s: w) System.out.println(s + ":" + w.get(s));
System.out.println("--");
w.shrinkToMaxSize(10);
for (StringBuilder s: w) System.out.println(s + ":" + w.get(s));
}
}