/*
* 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