/*
* Copyright 1996-2002 by Andruid Kerne. All rights reserved. CONFIDENTIAL. Use is subject to
* license terms.
*/
package ecologylab.collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import ecologylab.appframework.Memory;
import ecologylab.generic.ObservableDebug;
import ecologylab.generic.ThreadMaster;
/**
* Provides the facility of efficient weighted random selection from a set elements, each of whicn
* includes a characterizing floating point weight.
* <p>
*
* Works in cooperation w <code>SetElement</code>s; requiring that user object to keep an integer
* index slot, which only we should be accessing. This impure oo style is an adaption to Java's lack
* of multiple inheritance.
* <p>
*
* Gotta be careful to be thread safe. Seems no operations can safely occur concurrently. There are
* a bunch of synchronized methods to affect this.
**/
public class WeightSet<E extends AbstractSetElement> extends ObservableDebug implements Iterable<E>, Collection<E>
{
private static final int NO_MAX_SIZE = -1;
private final ArrayListX<E> arrayList;
private final HashSet<E> hashSet;
private final WeightingStrategy<E> weightingStrategy;
private final Comparator<E> comparator;
private int maxSize = NO_MAX_SIZE;
private static final int DEFAULT_SIZE = 16;
/**
* This might pause us before we do an expensive operation.
*/
ThreadMaster threadMaster;
public WeightSet (int maxSize, int initialSize, WeightingStrategy<E> weightingStrategy)
{
assert weightingStrategy != null;
this.hashSet = new HashSet<E>(initialSize);
this.arrayList = new ArrayListX<E>(initialSize);
this.maxSize = maxSize;
this.weightingStrategy = weightingStrategy;
this.comparator = new FloatWeightComparator(weightingStrategy);
// for (E e : arrayList)
// weightingStrategy.insert(e);
}
public WeightSet ( WeightingStrategy<E> getWeightStrategy )
{
this(NO_MAX_SIZE, DEFAULT_SIZE, getWeightStrategy);
}
public WeightSet (int maxSize, ThreadMaster threadMaster, WeightingStrategy<E> weightStrategy )
{
this(maxSize, maxSize, weightStrategy);
this.threadMaster = threadMaster;
}
// ///////////////////////////////////////////////////////
// DEFAULT COMPARATOR
// ///////////////////////////////////////////////////////
public final class FloatWeightComparator implements Comparator<E>
{
private WeightingStrategy<E> strat;
public FloatWeightComparator ( WeightingStrategy<E> getWeightStrategy )
{
strat = getWeightStrategy;
}
@Override
public int compare ( E o1, E o2 )
{
return Double.compare(strat.getWeight(o1), strat.getWeight(o2));
}
};
private void sortIfWeShould ( )
{
// TODO remove this check, make getWeightStrategy call sort?
if (weightingStrategy.hasChanged())
{
Collections.sort(arrayList, comparator);
setChanged();
notifyObservers();
weightingStrategy.clearChanged();
}
}
public synchronized double mean ( )
{
if (arrayList.size() == 0)
return 0;
double mean = 0;
for (E e : arrayList)
mean += weightingStrategy.getWeight(e);
return mean / arrayList.size();
}
private synchronized void clearAndRecycle (int start, int end)
{
int size = arrayList.size();
debug("^^^size before = " + size);
for (int i=end - 1; i>=start; i--)
{
E element = arrayList.get(i); // was remove(i), but that's inefficent
element.deleteHook();
element.recycle(true);// will also call deleteHook?!
}
// all of these elements are probably at the beginning of the list
// remove from there is worst case behavior of arrayList, because all of the higher elements
// must be moved.
// minimize this by doing it once.
//
// in case of CfContainers, recycle removes them from a set; however a weird case results in
// some of them not being removed, so this makes sure that all recycled elements are removed.
int expectedSize = size - (end - start);
int newSize = arrayList.size();
if (expectedSize < newSize)
{
int sizeDiff = newSize - expectedSize;
arrayList.removeRange(start, sizeDiff);
}
}
/**
* Selects the top weighted element from the set.<br>
* This method removes the selected from the set.
*
* @return
*/
public synchronized E maxSelect ( )
{
ArrayList<E> list = this.arrayList;
int size = list.size();
if (size == 0)
return null;
try
{
sortIfWeShould();
} catch (Throwable t)
{
t.printStackTrace();
boolean foundNulls = false;
for (int i=list.size() - 1; i>=0; i--)
{
E entry = list.get(i);
if (entry == null)
{
error("Oh my! Null Entry!!!!!");
list.remove(i);
foundNulls = true;
}
}
if (foundNulls)
return maxSelect();
}
return list.remove(--size);
}
public synchronized E maxPeek ( )
{
ArrayList<E> list = this.arrayList;
int size = list.size();
if (size == 0)
return null;
sortIfWeShould();
return list.get(--size);
}
public synchronized E maxPeek(int index)
{
ArrayList<E> list = this.arrayList;
int size = list.size();
if (size == 0)
return null;
sortIfWeShould();
int desiredIndex = size - index - 1;
return list.get(desiredIndex);
}
/**
* Default implementation of the prune to keep only maxSize elements
*/
public synchronized void prune()
{
prune(maxSize);
}
public synchronized void prune ( int numToKeep )
{
if (maxSize < 0)
return;
ArrayList<E> list = this.arrayList;
int numToDelete = list.size() - numToKeep;
if (numToDelete <= 0)
return;
debug("prune() -> "+numToKeep);
sortIfWeShould();
// List<E> deletionList = list.subList(0, numToDelete);
clearAndRecycle(0, numToDelete);
Memory.reclaim();
}
@Override
public boolean add(E el)
{
return insert(el);
}
public synchronized boolean insert ( E el )
{
boolean result = false;
if (!hashSet.contains(el))
{
weightingStrategy.insert(el);
result |= arrayList.add(el);
hashSet.add(el);
el.addSet(this);
el.insertHook();
}
return result;
}
public synchronized boolean remove ( E el )
{
boolean result = removeFromSet(el);
el.removeSet(this);
return result;
}
protected synchronized boolean removeFromSet ( E e )
{
weightingStrategy.remove(e);
boolean result = arrayList.remove(e);
hashSet.remove(e);
return result;
}
public E get(int i)
{
return arrayList.get(i);
}
/**
* Delete all the elements in the set, as fast as possible.
*
* @param doRecycleElements
* TODO
*
*/
public synchronized void clear ( boolean doRecycleElements )
{
synchronized (arrayList)
{
for (int i=size()-1; i>0; i--)
{
E e = arrayList.remove(i);
e.deleteHook();
if (doRecycleElements)
e.recycle(false);
}
arrayList.clear();
}
hashSet.clear();
}
/**
* Prune to the set's specified maxSize, if necessary, then do a maxSelect(). The reason for
* doing these operations together is because both require sorting.
*
* @return element in the set with the highest weight, or in case of a tie, a random pick of
* those.
*/
public synchronized E pruneAndMaxSelect ( )
{
prune(maxSize);
return maxSelect();
}
@Override
public int size ( )
{
return arrayList.size();
}
@Override
public String toString ( )
{
return super.toString() + "[" + size() + "]";
}
/**
* Check to see if the set has any elements.
*
* @return
*/
@Override
public boolean isEmpty ( )
{
return size() == 0;
}
/**
* Fetches the i'th element in the sorted list.
*
* @param i
* index into the list
* @return the element at index i
*/
public synchronized E at ( int i )
{
ArrayList<E> list = this.arrayList;
if (list.size() == 0 || i >= list.size())
return null;
sortIfWeShould();
return list.get(i);
}
/**
* Fetches the weight of the i'th element in the sorted list.
*
* @param i
* index into the list
* @return the weight of the element at index i
*/
public synchronized Double weightAt ( int i )
{
ArrayList<E> list = this.arrayList;
if (list.size() == 0)
return null;
sortIfWeShould();
return weightingStrategy.getWeight(list.get(i));
}
/**
* Fetches the highest weight in this set
* @return
*/
public synchronized Double maxWeight()
{
return weightAt(this.arrayList.size() - 1);
}
/**
* Fetches the weight of the passed in element.
*
* @param e
* Element to weigh. Doesn't have to be a member of the set.
* @return the weight of the element
*/
public double getWeight ( E e )
{
return weightingStrategy.getWeight(e);
}
/**
* Method Overriden by {@link cf.model.VisualPool VisualPool} to return true
*
* @return
*/
public boolean isRunnable ( )
{
return false;
}
public WeightingStrategy<E> getWeightStrategy ( )
{
return weightingStrategy;
}
@Override
public synchronized Iterator<E> iterator ( )
{
return arrayList.iterator();
}
@Override
public boolean addAll(Collection<? extends E> arg0)
{
boolean result = false;
for (E element: arg0)
result |= add(element);
return result;
}
@Override
public void clear()
{
clear(false);
}
@Override
public boolean contains(Object arg0)
{
// TODO Auto-generated method stub
return hashSet.contains(arg0);
}
@Override
public boolean containsAll(Collection<?> arg0)
{
throw new RuntimeException("not implemented.");
}
@Override
public boolean remove(Object arg0)
{
return remove((E) arg0);
}
@Override
public boolean removeAll(Collection<?> arg0)
{
boolean result = false;
for (Object o : arg0)
result |= remove(o);
return result;
}
@Override
public boolean retainAll(Collection<?> arg0)
{
throw new RuntimeException("not implemented.");
}
@Override
public Object[] toArray()
{
return arrayList.toArray();
}
@Override
public <T> T[] toArray(T[] arg0)
{
return arrayList.toArray(arg0);
}
public void toArray(E[] arg0, int start)
{
int pos = start;
for (int i = 0; i < size(); i++)
arg0[pos++] = arrayList.get(i);
}
}