/* * WeightedCollection.java * Copyright 2007 (c) Tom Parker <thpr@users.sourceforge.net> * Derived from WeightedList.java * Copyright 2006 (C) Aaron Divinsky <boomer70@yahoo.com> * * 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 library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package pcgen.base.util; import java.util.AbstractCollection; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; /** * An implementation of the <tt>Collection</tt> interface that allows objects * added to the Collection to have a "weight" associated with them. * This weight acts as though <i>weight</i> copies of the item were added to * the Collection. The {@code size()} method returns the total weight of * all items in the Collection. The {@code get()} method returns the * "weight" element in the Collection. * <p> * As an example, if three items are added to the Collection * <ul> * <li>Item 1, weight 3</li> * <li>Item 2, weight 2</li> * <li>Item 3, weight 1</li> * </ul> * The Collection will have a total weight of 3+2+1=6. The call * {@code get(4)} will return Item 2. * <p> * * @author boomer70 and Tom Parker (thpr@users.sourceforge.net) * @param <E> * The Class stored in the WeightedCollection * @see java.util.Collection */ public class WeightedCollection<E> extends AbstractCollection<E> { /** * The actual list where the data is stored. */ private Collection<WeightedItem<E>> theData; /** * Default constructor. Creates an empty collection. */ public WeightedCollection() { theData = new ListSet<>(); } /** * Constructs an empty collection with the specified initial capacity. * * @param initialSize * the initial capacity of the collection. * * @exception IllegalArgumentException * if the specified initial capacity is negative */ public WeightedCollection(int initialSize) { theData = new ListSet<>(initialSize); } /** * Creates a <tt>WeightedCollection</tt> from the <tt>Collection</tt> * provided. All the elements added will have the default weight equal to * the number of times they appear in the given collection. * * This constructor is both reference-semantic and value-semantic. It will * not modify or maintain a reference to the given Collection of objects. * However, references to the objects contained in the Collection are * maintained by the WeightedCollection, and the WeightedCollection may * return references to those objects contained in the Collection. * * @param collection * The <tt>Collection</tt> to copy. * @throws NullPointerException * if the given Collection is null */ public WeightedCollection(Collection<? extends E> collection) { this(); addAll(collection, 1); } /** * Constructs an empty WeightedCollection with the given Comparator used to * establish equality and order in the WeightedCollection. * * @param comp * The Comparator this Set will use to determine equality and * order of the WeightedCollection */ public WeightedCollection(Comparator<? super E> comp) { if (comp == null) { theData = new ListSet<>(); } else { theData = new TreeSet<>( new WeightedItemComparator<>(comp)); } } /** * Returns the total weight of the WeightedCollection. This is the sum of * the weights of all the items in the WeightedCollection. * * @return The total weight. */ @Override public int size() { int total = 0; for (WeightedItem<E> item : theData) { total += item.getWeight(); } return total; } /** * Adds all the elements from the specified <tt>Collection</tt> to this * WeightedCollection with the default weight of 1. * * This method is both reference-semantic and value-semantic. It will not * modify or maintain a reference to the given Collection of objects. * However, references to the objects contained in the Collection are * maintained by the WeightedCollection, and the WeightedCollection may * return references to those objects contained in the Collection. * * @param collection * The <tt>Collection</tt> to add the elements from. * @throws NullPointerException * if the given Collection is null * * @see java.util.List#addAll(java.util.Collection) */ @Override public boolean addAll(Collection<? extends E> collection) { return addAll(collection, 1); } /** * Adds an element to the WeightedCollection with the specified weight. If * the element is already present in the WeightedCollection the weight is * added to the existing element instead. Note that this is means * WeightedCollection does not guarantee order of the collection. * * @param weight * Weight to add this element with. * @param element * Element to add. * @return true if we added successfully * * @see java.util.List#add(int, java.lang.Object) * @throws IllegalArgumentException * if the given weight is less than zero */ public final boolean add(E element, int weight) { if (weight < 0) { throw new IllegalArgumentException("Cannot items with weight < 0"); } else if (weight == 0) { return false; } // Lets see if we can find this element for (WeightedItem<E> item : theData) { E wiElement = item.getElement(); if (wiElement == null && element == null || wiElement != null && wiElement.equals(element)) { item.addWeight(weight); return true; } } return theData.add(new WeightedItem<>(element, weight)); } /** * Adds the specified element with the default weight. * * @param element * The element to add * @return true if the element was added. * * @see WeightedCollection#add(Object, int) * @see java.util.List#add(java.lang.Object) */ @Override public boolean add(E element) { return add(element, 1); } /** * Returns a random selection from the WeightedCollection based on weight. * * @return The random element selected. */ public E getRandomValue() { int index = RandomUtil.getRandomInt(size()); int total = 0; for (WeightedItem<E> item : theData) { total += item.getWeight(); if (total > index) { /* * NOTE The return statement can't be 100% covered with a Sun * compiler for code coverage stats. */ return item.getElement(); } } /* * This can occur if the list is empty. */ throw new IndexOutOfBoundsException(index + " >= " + total); } /** * Returns an <tt>Iterator</tt> that iterates over the elements in the * WeightedCollection. This Iterator <i>accounts for the weight of the * elements in the WeightedCollection</i>. * * This method is reference-semantic. While ownership of the Iterator is * transferred to the calling object (no reference to the iterator is * maintained by the WeightedCollection), actions on the returned Iterator * (e.g. remove()) can alter the WeightedCollection on which this method was * called. * * @return An <tt>Iterator</tt> for the WeightedCollection. * * @see java.util.Collection#iterator() */ @Override public Iterator<E> iterator() { return new WeightedIterator(); } /** * Returns an <tt>Iterator</tt> that iterates over the elements in the * WeightedCollection. This Iterator <i>does NOT account for the weight of * the elements in the WeightedCollection</i>. Therefore in a list with * three elements of differing weights, this iterator simply returns each * element in turn. * * This method is reference-semantic. While ownership of the Iterator is * transferred to the calling object (no reference to the iterator is * maintained by the WeightedCollection), actions on the returned Iterator * (e.g. remove()) can alter the WeightedCollection on which this method was * called. * * @return An <tt>Iterator</tt> for the WeightedCollection. */ public Iterator<E> unweightedIterator() { return new UnweightedIterator(); } /** * Checks if the object specified exists in this WeightedCollection. * * @param element * The object to test for * @return <tt>true</tt> if the object is in the WeightedCollection. * * @see java.util.Collection#contains(java.lang.Object) */ @Override public boolean contains(Object element) { for (WeightedItem<E> item : theData) { E wiElement = item.getElement(); if (wiElement == null && element == null || wiElement != null && wiElement.equals(element)) { return true; } } return false; } /** * Returns the weight for the given object in this WeightedCollection. If * the given object is not in this collection, zero is returned. * * @param element * The object for which the weight in this WeightedCollection * will be returned. * @return the weight of the given object in this WeightedCollection, or * zero if the object is not in this WeightedCollection */ public int getWeight(Object element) { for (WeightedItem<E> item : theData) { E wiElement = item.getElement(); if (wiElement == null && element == null || wiElement != null && wiElement.equals(element)) { return item.theWeight; } } return 0; } /** * Removes the object from the WeightedCollection if it is present. This * removes the object from this WeightedCollection regardless of the weight * of the object in this WeightedCollection. Therefore, if an object was * weight 2 in this WeightedCollection and is removed, the size of this * WeightedCollection will decrease by two, and NO copies of the given * object will remain in this WeightedCollection. * * @param element * The element to remove * @return <tt>true</tt> if the element was removed. * * @see java.util.Collection#remove(java.lang.Object) */ @Override public boolean remove(Object element) { for (Iterator<WeightedItem<E>> it = theData.iterator(); it.hasNext();) { WeightedItem<E> item = it.next(); E wiElement = item.getElement(); if (wiElement == null && element == null || wiElement != null && wiElement.equals(element)) { it.remove(); return true; } } return false; } /** * Tests if this WeightedCollection has any elements. * * @return <tt>true</tt> if the WeightedCollection contains no elements. * * @see java.util.Collection#isEmpty() */ @Override public boolean isEmpty() { return theData.isEmpty(); } /** * Removes all the elements from the WeightedCollection. * * @see java.util.Collection#clear() */ @Override public void clear() { theData.clear(); } /** * Compares the specified object with this WeightedCollection for equality. * Returns <tt>true</tt> if and only if the specified object is also a * WeightedCollection, both WeightedCollections have the same size, and all * corresponding pairs of elements in the two WeightedCollections are * <i>equal</i>. (Two elements <tt>e1</tt> and <tt>e2</tt> are <i>equal</i> * if <tt>(e1==null ? e2==null : * e1.equals(e2))</tt>.) In other words, * two WeightedCollections are defined to be equal if they contain the same * elements in the same order. * <p> * * @param obj * The object to be compared for equality with this * WeightedCollection. * @return <tt>true</tt> if the specified object is equal to this * WeightedCollection. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { /* * CONSIDER Currently, this is ORDER SENSITIVE, which is probably bad * for a collection? This needs to be seriously thought through to * determine how exactly this should work... especially given that there * is no solution for sorting a WeightedCollection and thus it is not * possible to actually sort before doing the comparison. - thpr 2/5/07 */ return obj instanceof WeightedCollection && theData.equals(((WeightedCollection<?>) obj).theData); } /** * Returns the hash code value for this WeightedCollection. * <p> * * @return the hash code value for this WeightedCollection. * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return theData.hashCode(); } /** * Returns a string representation of the WeightedCollection. * * @return A string representation of the values in the WeightedCollection. * * @see java.lang.Object#toString() */ @SuppressWarnings("nls") @Override public String toString() { return "WeightedCollection: " + theData.toString(); } /** * Adds each element in the specified collection with the indicated weight * value. * * This method is both reference-semantic and value-semantic. It will not * modify or maintain a reference to the given Collection of objects. * However, references to the objects contained in the Collection are * maintained by the WeightedCollection, and the WeightedCollection may * return references to those objects contained in the Collection. * * @param weight * The weight value to use for each element added. * @param collection * The elements to add to the WeightedCollection * @return <tt>true</tt> if the WeightedCollection is changed by this * call. * @throws NullPointerException * if the given Collection is null * * @see java.util.List#addAll(int, java.util.Collection) */ public final boolean addAll(Collection<? extends E> collection, int weight) { boolean modified = false; for (E element : collection) { modified |= add(element, weight); } return modified; } /** * This class is a simple wrapper to associate an object from a * <tt>WeightedList</tt> and its weight. * * @author boomer70 * * @param <T> */ static class WeightedItem<T> { private final T theElement; private int theWeight; /** * This constructor creates a new <tt>WeightedItem</tt> with the * specified weight. * * @param element * The object this Item represents. * @param weight * The weight of the item within the list. */ public WeightedItem(T element, int weight) { theElement = element; theWeight = weight; } /** * Gets the wrapped object. * * @return The object this item wraps */ public final T getElement() { return theElement; } /** * Gets the weight of this object. * * @return The weight of this item */ public final int getWeight() { return theWeight; } /** * Adds the specified amount of weight to the item. * * @param weight * an amount of weight to add. */ public void addWeight(int weight) { theWeight += weight; } @Override public int hashCode() { return theWeight * 29 + (theElement == null ? 0 : theElement.hashCode()); } /** * Equals method. Note this is required in order to have the .equals() * at the WeightedCollection level work properly (it is a deep equals) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof WeightedItem) { WeightedItem<?> item = (WeightedItem<?>) obj; return theWeight == item.theWeight && (theElement == null && item.theElement == null || theElement != null && theElement.equals(item.theElement)); } //Arguably unreachable code return false; } @Override public String toString() { return theElement + " (" + theWeight + ")"; } } /** * A weighted Iterator for a WeightedCollection */ private class WeightedIterator implements Iterator<E> { private final Iterator<WeightedItem<E>> iter = theData.iterator(); private WeightedItem<E> currentEntry; private int currentReturned = 0; @Override public boolean hasNext() { if (currentEntry == null) { if (!iter.hasNext()) { return false; } currentEntry = iter.next(); currentReturned = 0; } if (currentReturned < currentEntry.theWeight) { return true; } return iter.hasNext(); } @Override public E next() { if (currentEntry == null || currentReturned >= currentEntry.theWeight) { currentEntry = iter.next(); currentReturned = 0; } currentReturned++; return currentEntry.theElement; } @Override public void remove() { iter.remove(); currentEntry = null; } } /** * A Unweighted Iterator for the WeightedCollection */ private class UnweightedIterator implements Iterator<E> { /** An iterator that iterates over the raw data elements. */ private final Iterator<WeightedItem<E>> realIterator = theData .iterator(); /** * Checks if there are any more elements in the iteration. * * @return <tt>true</tt> if there are more elements. * * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return realIterator.hasNext(); } /** * Returns the next element in the iteration. * * @return The next element. * * @see java.util.Iterator#next() */ @Override public E next() { return realIterator.next().getElement(); } /** * Removes from the WeightedCollection the last element returned from * the iteration. * * @see java.util.Iterator#remove() */ @Override public void remove() { realIterator.remove(); } } /** * Implements a Comparator of WeightedItems. Takes in a Comparator to which * it will delegate comparison of the underlying objects. * * @param <WICT> * The type of the object underlying the WeightedItem objects * that this WeightedItemComparator can compare. */ private static class WeightedItemComparator<WICT> implements Comparator<WeightedItem<WICT>> { /** * The Comparator to which this WeightedItemComparator will delegate * comparison of the underlying objects. */ private final Comparator<? super WICT> delegate; /** * Constructs a new WeightedItemComparator with the given Comparator as * the delegate given to provide comparison of the underlying objects. * * @param comp * The delegate Comparator given to provide comparison of the * objects underlying the WeightedItem objects compared by * this WeightedItemComparator */ public WeightedItemComparator(Comparator<? super WICT> comp) { delegate = comp; } /** * Compare two WeightedItem objects * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override public int compare(WeightedItem<WICT> item1, WeightedItem<WICT> item2) { return delegate.compare(item1.getElement(), item2.getElement()); } } }