/**
*
*/
package cz.cuni.mff.peckam.java.origamist.unused.utils;
import java.util.Comparator;
import java.util.Map;
import java.util.SortedMap;
import org.apache.log4j.Logger;
/**
* A red-black tree supporting searching for epsilon-equal values.
*
* @param K Type of keys.
* @param V Type of values
* @param E Type of epsilon.
*
* @author Martin Pecka
*/
public class EpsilonRedBlackTree<K, V, E> extends RedBlackTree<K, V> implements EpsilonMap<K, V>
{
/** */
private static final long serialVersionUID = -5978298665466857457L;
/** The comparator to be used for searching epsilon-equal keys. */
protected EpsilonComparator<? super K, E> epsilonComparator;
/**
* Construct a new EpsilonRedBlackTree with the default comparator. The default comparator needs all values inserted
* into or deleted from the tree to implement {@link Comparable}<K>. If an element doesn't implement it, an
* insert or delete method will end up throwing a {@link ClassCastException}. Also a default epsilon-comparator is
* created using the given epsilon and K must implement {@link Number}.
*
* @param epsilon The epsilon used for defining epsilon-equal keys.
*/
public EpsilonRedBlackTree(E epsilon)
{
super();
epsilonComparator = getDefaultEpsilonComparator(epsilon);
}
/**
* Construct a new EpsilonRedBlackTree using the given comparator. The comparator must be able to compare every two
* "valid" keys, and must throw a {@link ClassCastException} if an "invalid" key is to be compared. Also a default
* epsilon-comparator is created using the given epsilon and K must implement {@link Number}.
*
* @param comparator The comparator to use for comparing this tree's element's keys.
* @param epsilon The epsilon used for defining epsilon-equal keys.
*/
public EpsilonRedBlackTree(Comparator<? super K> comparator, E epsilon)
{
super(comparator);
epsilonComparator = getDefaultEpsilonComparator(epsilon);
}
/**
* Create a new EpsilonRedBlackTree with entries from the given map. Use the default comparator to compare the keys.
* The keys must implement {@link Comparable}<K>. Also a default epsilon-comparator is created using the given
* epsilon and K must implement {@link Number}.
*
* @param m The map to take entries from.
* @param epsilon The epsilon used for defining epsilon-equal keys.
*
* @throws ClassCastException If a key from the given map doesn't implement the {@link Comparable}<K>
* interface.
* @throws NullPointerException If <code>m == null</code> or if the map contains a <code>null</code> key and the
* comparator doesn't support it.
*/
public EpsilonRedBlackTree(Map<? extends K, ? extends V> m, E epsilon)
{
super(m);
epsilonComparator = getDefaultEpsilonComparator(epsilon);
}
/**
* Create a new EpsilonRedBlackTree with entries from the given sorted map. The map's comparator is used. This is an
* effective (linear time) algorithm. Also a default epsilon-comparator is created using the given epsilon and K
* must implement {@link Number}.
*
* @param m The sorted map to take entries from.
* @param epsilon The epsilon used for defining epsilon-equal keys.
*/
public EpsilonRedBlackTree(SortedMap<K, ? extends V> m, E epsilon)
{
super(m);
epsilonComparator = getDefaultEpsilonComparator(epsilon);
}
/**
* Construct a new EpsilonRedBlackTree with the default comparator. The default comparator needs all values inserted
* into or deleted from the tree to implement {@link Comparable}<K>. If an element doesn't implement it, an
* insert or delete method will end up throwing a {@link ClassCastException}.
*
* @param epsilonComparator The comparator of epsilon-equal keys.
*/
public EpsilonRedBlackTree(EpsilonComparator<? super K, E> epsilonComparator)
{
super();
this.epsilonComparator = epsilonComparator;
}
/**
* Construct a new EpsilonRedBlackTree using the given comparator. The comparator must be able to compare every two
* "valid" keys, and must throw a {@link ClassCastException} if an "invalid" key is to be compared.
*
* @param comparator The comparator to use for comparing this tree's element's keys.
* @param epsilonComparator The comparator of epsilon-equal keys.
*/
public EpsilonRedBlackTree(Comparator<? super K> comparator, EpsilonComparator<? super K, E> epsilonComparator)
{
super(comparator);
this.epsilonComparator = epsilonComparator;
}
/**
* Create a new EpsilonRedBlackTree with entries from the given map. Use the default comparator to compare the keys.
* The keys must implement {@link Comparable}<K>.
*
* @param m The map to take entries from.
* @param epsilonComparator The comparator of epsilon-equal keys.
*
* @throws ClassCastException If a key from the given map doesn't implement the {@link Comparable}<K>
* interface.
* @throws NullPointerException If <code>m == null</code> or if the map contains a <code>null</code> key and the
* comparator doesn't support it.
*/
public EpsilonRedBlackTree(Map<? extends K, ? extends V> m, EpsilonComparator<? super K, E> epsilonComparator)
{
super(m);
this.epsilonComparator = epsilonComparator;
}
/**
* Create a new EpsilonRedBlackTree with entries from the given sorted map. The map's comparator is used. This is an
* effective (linear time) algorithm.
*
* @param m The sorted map to take entries from.
* @param epsilonComparator The comparator of epsilon-equal keys.
*/
public EpsilonRedBlackTree(SortedMap<K, ? extends V> m, EpsilonComparator<? super K, E> epsilonComparator)
{
super(m);
this.epsilonComparator = epsilonComparator;
}
@Override
public V epsilonGet(K key)
{
TreePath path = epsilonGetPath(key);
if (path.size() > 0 && epsilonEquals(path.getLast().getKey(), key))
return path.getLast().getValue();
return null;
}
/**
* If <code>surelyContains == false</code>, then this acts like {@link EpsilonRedBlackTree#epsilonGet(Object)}.
* Otherwise, if the standard epsilonGet() returns no result, it tries to scan the tree sequentially and find the
* epsilon-equal key.
*
* This is needed due to rounding errors. Figure out the following situation:
*
* <pre>
*
* (5,0) a rotation (0,EPS) and then try to search for (5,-EPS)...
* / \ ==========> \ although it is epsilon-equal to (5,0), -EPS isn't epsilon-equal to EPS
* (0,EPS) (X1,Y1) (5,0) so the standard search algorithm would direct you to the left from the root
* \
* (X1,Y1)
* </pre>
*
* @param key The key to search for.
* @param surelyContains If <code>true</code> and the standard epsilonGet() finds nothing, a sequential search is
* performed.
* @return The value corresponding to the given key, or <code>null</code> if no such value exists.
*/
public V epsilonGet(K key, boolean surelyContains)
{
V result = epsilonGet(key);
if (result != null || epsilonContainsKey(key))
return result;
if (surelyContains) {
Logger.getLogger(getClass()).warn("Performing sequential search.");
TreePath path = epsilonGetPathSequentially(key);
if (path.size() > 0)
return path.getLast().getValue();
}
return null;
}
/**
* Search sequentially for the entry with epsilon-equal key.
*
* @param key The key to search for.
* @return The path to the entry with the epsilon-equal key.
*/
protected TreePath epsilonGetPathSequentially(K key)
{
if (root == null)
return new TreePath();
TreePath path = epsilonGetPath(firstKey());
while (path.size() > 0) {
if (epsilonComparator.compare(path.getLast().key, key) == 0)
return path;
path.moveToSuccesor();
}
return path;
}
@Override
public V epsilonPut(K key, V value)
{
if (root == null) {
return super.put(key, value);
}
TreePath path = epsilonGetPath(key);
int cmp = epsilonComparator.compare(key, path.getLast().getKey());
if (cmp == 0)
return path.getLast().setValue(value);
// now we have the new entry's parent as the last item on the path
RedBlackTree<K, V>.Entry e = ((RedBlackTree<K, V>) this).new Entry(key, value);
if (cmp < 0)
path.getLast().left = e;
else
path.getLast().right = e;
path.addLast(e);
repairTreeAfterInsert(path);
size++;
modCount++;
return null;
}
@Override
public V epsilonRemove(K key)
{
TreePath path = epsilonGetPath(key);
if (!epsilonEquals(path.getLast().getKey(), key))
return null;
V oldValue = path.getLast().value;
deleteLastPathEntry(path);
return oldValue;
}
/**
* If <code>surelyContains == false</code>, then this acts like {@link EpsilonRedBlackTree#epsilonRemove(Object)}.
* Otherwise, if the standard epsilonRemove() returns no result, it tries to scan the tree sequentially and find the
* epsilon-equal key.
*
* This is needed due to rounding errors. Figure out the following situation:
*
* <pre>
*
* (5,0) a rotation (0,EPS) and then try to delete (5,-EPS)...
* / \ ==========> \ although it is epsilon-equal to (5,0), -EPS isn't epsilon-equal to EPS
* (0,EPS) (X1,Y1) (5,0) so the standard search algorithm would direct you to the left from the root
* \
* (X1,Y1)
* </pre>
*
* @param key The key to delete.
* @param surelyContains If <code>true</code> and the standard epsilonRemove() finds nothing, a sequential search is
* performed.
* @return The value corresponding to the removed key, or <code>null</code> if no such value exists.
*/
public V epsilonRemove(K key, boolean surelyContains)
{
TreePath path = epsilonGetPath(key);
if (epsilonEquals(path.getLast().getKey(), key)) {
V oldValue = path.getLast().value;
deleteLastPathEntry(path);
return oldValue;
} else {
if (surelyContains) {
Logger.getLogger(getClass()).warn("Sequential search performed for remove.");
path = epsilonGetPathSequentially(key);
if (path.size() > 0) {
V value = path.getLast().getValue();
deleteLastPathEntry(path);
return value;
}
}
return null;
}
}
/**
* Get the path to an entry with epsilon-equal key.
*
* @param key The key to find path for.
* @return The path to an entry with epsilon-equal key. If no epsilon-equal key is found, the path to the point
* where the key should be inserted is returned.
*/
protected TreePath epsilonGetPath(K key)
{
TreePath path = new TreePath();
RedBlackTree<K, V>.Entry e = this.root;
while (e != null) {
path.addLast(e);
int cmp = epsilonComparator.compare(key, e.key);
if (cmp < 0)
e = e.left;
else if (cmp > 0)
e = e.right;
else
break;
}
return path;
}
/**
* Return true if the given keys are epsilon-equal.
*
* @param k1 The first key to compare.
* @param k2 The second key to compare.
* @return true if the given keys are epsilon-equal.
*/
protected boolean epsilonEquals(K k1, K k2)
{
return epsilonComparator.compare(k1, k2) == 0;
}
@Override
public boolean epsilonContainsKey(K key)
{
TreePath path = epsilonGetPath(key);
return path.size() > 0 && epsilonEquals(path.getLast().getKey(), key);
}
@Override
public void epsilonPutAll(Map<? extends K, ? extends V> map)
{
if (size() == 0) {
super.putAll(map);
} else {
for (Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
put(e.getKey(), e.getValue());
}
}
}
@Override
public boolean epsilonEquals(Map<? extends K, ? extends V> map)
{
if (size() != map.size())
return false;
if (size() == 0)
return true;
for (Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
TreePath path = epsilonGetPath(e.getKey());
if (path.size() == 0)
return false;
if (!epsilonEquals(path.getLast().getKey(), e.getKey()))
return false;
if (!valEquals(path.getLast().getValue(), e.getValue()))
return false;
}
return true;
}
/**
* A default comparator for epsilon-comparison. E must implement {@link Number}.
*
* @param epsilon The epsilon to use for comparison.
* @return A default comparator for epsilon-comparison. E must implement {@link Number}.
*/
protected EpsilonComparator<? super K, E> getDefaultEpsilonComparator(final E epsilon)
{
return new EpsilonComparator<K, E>() {
protected double eps = ((Number) epsilon).doubleValue();
@Override
public int compare(K o1, K o2)
{
return compare(o1, o2, eps);
}
@Override
public int compare(K o1, K o2, E epsilon)
{
return compare(o1, o2, ((Number) epsilon).doubleValue());
}
public int compare(K o1, K o2, double eps)
{
if (o1 == null) {
if (o2 == null)
return 0;
else
return -1;
}
if (o2 == null)
return 1;
Number o1n = (Number) o1;
Number o2n = (Number) o2;
Double diff = o1n.doubleValue() - o2n.doubleValue();
if (diff.compareTo(eps) > 0)
return 1;
else if (diff.compareTo(-eps) < 0)
return -1;
else
return 0;
}
};
}
}