/* * MultiSet.java 2004-11-05 * * Dirk Ansorge, FernUniversitaet Hagen * */ package twodsack.util.collection; import twodsack.util.collectiontype.*; import java.util.Comparator; import java.util.TreeSet; import java.util.SortedSet; import java.util.Iterator; import java.io.Serializable; /** * A MultiSet provides a tree structure to store elements. Moreover, in a * MultiSet duplicate elements can be stored (which cannot be stored in structures * like {@link java.util.TreeSet} directly. Nevertheless, MultiSet extends * TreeSet. In a MultiSet only objects of type {@link twodsack.util.collectiontype.MultiSetEntry} can be * stored. Such types implement the interface {@link twodsack.util.ComparableMSE}, so a total order * is defined.
* Usually, a MultiSet is not used directly, but is extended. An example for this is * {@link twodsack.set.ElemMultiSet}. */ public class MultiSet implements Serializable { /* * fields */ private TreeSet ts; private Comparator comparatorTS; /** * The size of the set. */ public int size; /* * constructors */ /** * Default constructor. * Constructs new instance and sets default values. */ public MultiSet () { ts = new TreeSet(); size = 0; comparatorTS = null; } /** * Standard constructor. * A {@link Comparator} must be specified for the construction of a MultiSet, since * the elements cannot be stored in order otherwise. * * @param comp the Comparator */ public MultiSet (Comparator comp) { ts = new TreeSet(comp); this.comparatorTS = comp; size = 0; } /* * methods */ /** * Adds an Object o. * First, the object o is wrapped in a {@link MultiSetEntry}. * Then, if the object is already present in the structure, the counter for * this object is increased. Otherwise, the wrapped object is inserted in the structure. * * @param o the object to be added */ public void add(Object o) { MultiSetEntry compO = new MultiSetEntry(o,1); this.size++; if (!ts.add(compO)) { SortedSet ss = ts.tailSet(compO); ((MultiSetEntry)ss.first()).number++; }//else }//end method add /** * Adds an Object o n times. * First, the object o is wrapped in a {@link MultiSetEntry}. * Then, if the object is already present in the structure, the counter for * this object is increased by n. Otherwise, the wrapped object is inserted in the structer with * counter = n. * * @param o the object to be added * @param n the number of instances to be added */ public void add(Object o, int n) { MultiSetEntry compO = new MultiSetEntry(o,n); this.size += n; if (!ts.add(compO)) { SortedSet ss = ts.tailSet(compO); ((MultiSetEntry)ss.first()).number += n; }//if }//end method add /** * Adds an object which is already wrapped in a {@link MultiSetEntry}. * This method is used with {@link addAll}. * * @param mse the MultiSetEntry */ private void add(MultiSetEntry mse) { this.size += mse.number; if (!ts.add(mse)) { SortedSet ss = ts.tailSet(mse); ((MultiSetEntry)ss.first()).number += mse.number; }//if }//end method add /** * Adds all objects stored in m. * * @param m the MultiSet that holds the elements which have to be stored */ public void addAll(MultiSet m) { Iterator it = m.iterator(); MultiSetEntry actEntry; while (it.hasNext()) { actEntry = (MultiSetEntry)it.next(); add(actEntry); }//while }//end method addAll /** * Adds all objects stored in arr. * * @param arr the array that holds the elements which have to be stored. */ public void add(Object[] arr) { for (int i = 0; i < arr.length; i++) add(arr[i]); }//end method add /** * Deletes all elements in this. * Sets the size of this to 0. */ public void clear() { this.size = 0; ts.clear(); }//end method clear /** * Produces a clone of this. * Note, that a clone is not a copy, i.e. the TreeSet, which stores * the elements is not copied, but is the same as in the original. * * @return the resulting TreeSet as Object */ public Object clone() { System.out.println("MS.clone() called!"); MultiSet retSet; if (!(comparatorTS == null)) { retSet = new MultiSet(comparatorTS); retSet.comparatorTS = this.comparatorTS; }//if else { retSet = new MultiSet(); retSet.comparatorTS = null; }//else retSet.ts = (TreeSet)this.ts.clone(); return retSet; }//end method clone /** * Returns the Comparator which is used to keep the structure in order. * * @return the comparator as Comparator. */ public Comparator comparator() { return this.comparatorTS; }//end method comparator /** * Returns true if o is element of this structure. * false otherwise. * * @param o the object that has to be found in the structure * @return {true, false} depending on the existance of o in * this */ public boolean contains (Object o) { return ts.contains(new MultiSetEntry(o,1)); }//end method contains /** * Returns the first object of the structure w.r.t. the comparator. * The returned object is not a MultiSetEntry but the value stored in it. * * @return the first object */ public Object first () { return ((MultiSetEntry)ts.first()).value; }//end method first /** * Returns the first MultiSetEntry of the structure w.r.t. the comparator. * * @return the first MultiSetEntry */ public MultiSetEntry firstMSE() { return (MultiSetEntry)ts.first(); }//end method firstMSE /** * Returns true if the underlying {@link TreeSet} is empty. * false else. * * @return {true, false} depending on the size of this */ public boolean isEmpty() { return ts.isEmpty(); }//end method isEmpty /** * Returns an iterator for the elements in the structure. * Note: If the remove method of class Iterator is used, * the field variable size is not updated properly. Use {@link #recomputeSize()} * in that case. * * @return the iterator as {@link java.util.Iterator} */ public Iterator iterator() { return ts.iterator(); }//end method iterator /** * Returns the last object of the structure w.r.t. the comparator. * * @return the last object */ public Object last() { return ((MultiSetEntry)ts.last()).value; }//end method last /** * For a multiple entry, remove one of them. * If the entry exists only once, remove it completely. * If the entry doesn't exist, do nothing. * * @param o the entry that shall be deleted */ public void removeOneEntry(Object o) {MultiSetEntry compO = new MultiSetEntry(o,1); if (ts.contains(compO)) { SortedSet ss = ts.tailSet(compO); if (((MultiSetEntry)ss.first()).number == 1) ts.remove(compO); else { ((MultiSetEntry)ss.first()).number--; }//else this.size--; }//if }//end method remove /** * For a multiple entry, remove it completely. * If the entry doesn't exist, do nothing. * * @param o the entry that shall be deleted */ public void removeAllOfThisKind(Object o) { MultiSetEntry compO = new MultiSetEntry(o,1); SortedSet ss = ts.tailSet(compO); int num = ((MultiSetEntry)ss.first()).number; this.size = this.size - num; ts.remove(compO); }//end method removeAllOfThisKind /** * For a multiple entry, remove it completely. * If the entry doesn't exist, do nothing. This version is much faster than the removeAllOfThisKind(Object) * version. * * @param o the entry that shall be deleted * @param num the number of entries that the object has */ public void removeAllOfThisKind(Object o, int num) { MultiSetEntry compO = new MultiSetEntry(o,1); ts.remove(compO); this.size = this.size - num; }//end method removeAllOfThisKind /** * Returns the number of elements stored in the structure. * For a structure which holds the elements (1,1,1,2,2,3) the size would be 6. * Caution: This operator may return the wrong size if remove was applied on an Iterator of * this. To be sure to get the right result, call {@link #recomputeSize()} before. * * @return the size as int */ public int size() { return this.size; }//end method size /** * Stores all elements of the structure in an array. * This is an expensive method for a big tree structure. Don't use this intensively. * * @return an array of (sorted) objects */ public Object[] toArray() { Object[] retArr = new Object[this.size]; Iterator it = ts.iterator(); int index = 0; while (it.hasNext()) { MultiSetEntry actEntry = (MultiSetEntry)it.next(); int num = actEntry.number; for (int i = 0; i < num; i++) { retArr[index] = actEntry.value; index++; }//for }//while return retArr; }//end method toArray /** * Compute the size of the structure and store it. * This is just to be safe. Use this method if Iterator.remove() is used. * */ public void recomputeSize() { //computes and sets the size of this Iterator it = ts.iterator(); int newSize = 0; while (it.hasNext()) { newSize = newSize + ((MultiSetEntry)it.next()).number; } this.size = newSize; }//end method recomputeSize /** * Returns the underlying {@link java.util.TreeSet} * * @return the TreeSet */ public TreeSet treeSet() { return this.ts; }//end method treeSet /** * Sets the TreeSet of this to ts. * Afterwards, the size is re-computed. * * @param ts the new TreeSet */ public void setTreeSet(TreeSet ts) { this.ts = ts; recomputeSize(); }//end method setTreeSet /** * Returns true if any element stored in this appears at least twice. * * @return true if any element appears at least twice */ public boolean hasDuplicates() { Iterator it = this.iterator(); MultiSetEntry mse; while (it.hasNext()) { mse = (MultiSetEntry)it.next(); if (mse.number > 1) return true; }//while return false; }//end method hasDuplicates }//end class MultiSet