All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.util.TernarySearchTree Maven / Gradle / Ivy

There is a newer version: 7.4.0
Show newest version
//
// 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
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2004 - 2010. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import net.sf.eBus.util.regex.Component;
import net.sf.eBus.util.regex.Pattern;

/**
 * Ternary search tree (TST)-based implementation of the
 * {@link java.util.Map} interface. This implementation provides all
 * optional interface methods, allows {@code null} values
 * but restricts keys to non-{@code null java.lang.CharSequence}
 * objects.
 * 

* It must be pointed out that this implementation * of {@link #entrySet()}, {@link #keySet()} and * {@link #values()} does not return sets backed by the * map. Changes to the map are not reflected in the returned set * nor are changes to the returned set reflected in the map. *

*

* Note: this implementation is not synchronized. If * multiple threads currently access this map and at least one * thread modifies the map by adding or removing an entry, then * this map must be externally synchronized. External * synchronization is accomplished in two ways: *

*
    *
  1. * Placing the map inside a {@code synchronized} block: *
     *       
     *         synchronized (tstMap)
     *         {
     *             tstMap.put("abcd", obj);
     *         }
     *       
     *     
    *
  2. *
  3. * Constructing a {@code TernarySearchTree} within a * {@code java.util.Collections.synchronizedMap()}: *
     *       
     *         Map m =
     *             Collections.synchronizedMap(
     *                 new TernarySearchTree(...));
     *       
     *     
    *
  4. *
*

* For more information on ternary search trees, see * Bentley, J., and Sedgewick, R. Fast algorithms for sorting * and searching strings. In Eighth Annual ACM-SIAM Symposium * on Discrete Algorithms (1997), SIAM Press. *

* * @param Value type. * * @author Charles Rapp */ public final class TernarySearchTree implements Map, Serializable { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * The left child node is stored in index zero. */ private static final int LEFT = 0; /** * The center child node is stored in index one. */ private static final int CENTER = 1; /** * The right child node is stored in index two. */ private static final int RIGHT = 2; /** * A ternary search tree node has three children nodes. */ private static final int CHILD_COUNT = 3; /** * Serialization version identifier. */ private static final long serialVersionUID = 0x050200L; //----------------------------------------------------------- // Locals. // /** * The tree's root node. An empty tree will have a null root. * This field is not serialized directly because writeObject * serializes only the key mappings. */ private TSTNode mRoot; /** * The number of keys in the tree. */ private int mSize; /** * The number of allocated nodes. */ private long mNodeCount; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Constructs an empty ternary search tree map. */ public TernarySearchTree() { mRoot = null; mSize = 0; mNodeCount = 0; } // end of TernarySearchTree() /** * Construct a new {@code TernarySearchTree} with the same * mappings as the specified {@code Map}. * @param m Copies mappings from here. */ public TernarySearchTree( final Map m) { m.entrySet().stream(). forEach((entry) -> { put(entry.getKey(), entry.getValue()); }); } // end of TernarySearchTree(Map<>) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Map Interface Implementation. // /** * Returns {@code true} if this map contains no * key-value mappings and {@code false} otherwise. * @return {@code true} if this map contains no * key-value mappings and {@code false} otherwise. */ @Override public boolean isEmpty() { return (mSize == 0); } // end of isEmpty() /** * Returns the number of key-value mappings in this tree. * @return the number of key-value mappings in this tree. */ @Override public int size() { return (mSize); } // end of size() /** * Returns the number of nodes used in this map. * @return the number of nodes used in this map. */ public long nodeCount() { return (mNodeCount); } // end of nodeCount() /** * Clears out all stored values, leaving an empty map. */ @Override public void clear() { if (mRoot != null) { mRoot.clear(); mSize = 0; mNodeCount = 0; } return; } // end of clear() /** * Returns {@code true} if the key is in the ternary * search tree and {@code false} otherwise. * @param key Search for this key in the tree. * @return {@code true} if the key is in the ternary * search tree and {@code false} otherwise. */ @Override public boolean containsKey(final Object key) { boolean retcode = false; if (key == null) { throw (new IllegalArgumentException("null key")); } else { final TSTNode node = findNode((CharSequence) key); if (node != null) { retcode = node.isKey(); } } return (retcode); } // end of containsKey(Object) /** * Returns {@code true} if {@code value} is stored in the * ternary search tree and {@code false} otherwise. * @param value Searches for this value. * @return {@code true} if {@code value} is stored in the * ternary search tree and {@code false} otherwise. */ @Override public boolean containsValue(final Object value) { return ( mRoot == null ? false : mRoot.containsValue(value)); } // end of containsValue(Object) /** * Returns the value associated with {@code key}. * If the ternary search tree does not contain the key, * then returns {@code null}. * @param key Search for this key. * @return the value associated with {@code key}. * If the ternary search tree does not contain the key, * then returns {@code null}. */ @Override public V get(final Object key) { V retval = null; if (key == null) { throw (new IllegalArgumentException("null key")); } else { final TSTNode node = findNode((CharSequence) key); if (node != null) { retval = node.value(); } } return (retval); } // end of get(Object) /** * Enters a value into the ternary search tree using the * text key. If the key is already in the tree, then * replaces the existing value with the new value and * returns the replaced value. If the key is not in the * tree, then {@code null} is returned. * @param key The text key. * @param value The key's associated value. * @return the previous value stored under {@code key}. May * return {@code null}. */ @Override public V put(final CharSequence key, final V value) { TSTNode node; TSTNode node2 = null; int index; final int length = key.length(); int child = LEFT; V retval = null; // Match as much of the key as already exists in the // tree. for (node = mRoot, index = 0; node != null && index < length; index += ((Math.abs(child) * -1) + 1), node2 = node, node = node.child(child + 1)) { child = node.split(key.charAt(index)); } // If we have reached the end of the key, then this key // is already in the tree. if (index == length) { // Back up and use the previous node. node = node2; } // Place the remainder of the key into the tree. else { // Handle the first node separately because we may // have to set the root node. if (node2 == null) { mRoot = new TSTNode<>(key.charAt(index)); node = mRoot; } else { node = new TSTNode<>(key.charAt(index)); // Make sure you place the node in proper place // the first time. node2.child((child + 1), node); } ++index; ++mNodeCount; for (; index < length; node = node2, ++index, ++mNodeCount) { node2 = new TSTNode<>(key.charAt(index)); node.child(CENTER, node2); } } // Make sure that node is not null. if (node != null) { // Make sure this node is a key node. if (node.isKey() == false) { node.key(true, key); ++mSize; } retval = node.value(value); } return (retval); } // end of put(CharSequence, V) /** * Copies all the mappings from the specified map to this * tree. This method does nothing more than iterate over * the specified map, calling * {@link #put(CharSequence, Object)} successively. * @param map The copied map. */ @Override public void putAll( final Map map) { map.entrySet().stream(). forEach((entry) -> { put(entry.getKey(), entry.getValue()); }); return; } // end of putAll(Map<>) /** * Removes the key-value mapping from the tree and returns * the now removed value. * @param key Remove the mapping at this key. * @return the removed value or {@code null} if {@code key} * is not in the tree. */ @Override public V remove(final Object key) { V retval = null; if (key == null) { throw (new IllegalArgumentException("null key")); } else { final TSTNode node = findNode((CharSequence) key); if (node != null) { retval = node.value(null); node.key(false, null); --mSize; } } return (retval); } // end of remove(Object) /** * Returns all keys currently stored in the tree. If the * tree is empty, then an empty set is returned. This set is * not backed by the tree. Changes to the returned * set are not reflected in the tree and changes to the tree * are not reflected in the set. * @return all keys currently stored in the tree. */ @Override public Set keySet() { final Set retval = new HashSet<>(); if (mRoot != null) { final Set> entries = entrySet(); // Put the keys into the key set return value. entries.stream(). forEach((entry) -> { retval.add(entry.getKey()); }); } return (retval); } // end of keySet() /** * Returns the words matching the query. This set is * not backed by the tree. Changes to the returned * set are not reflected in the tree and changes to the tree * are not reflected in the set. * @param query Match against this query. * @return the words matching the query. */ public Set keySet(final Pattern query) { final Set retval = new HashSet<>(); if (mRoot != null) { final Set> entries = entrySet(query); // Put the keys into the key set return value. entries.stream() .forEach( entry -> retval.add(entry.getKey())); } return (retval); } // end of keySet(Pattern) /** * Returns at most {@code maxMatches} words matching the * query. This set is not backed by the tree. * Changes to the returned set are not reflected in the tree * and changes to the tree are not reflected in the set. * @param query Match against this query. * @param maxMatches Match at most this many keys. * @return the words matching the query. * @exception IllegalArgumentException * if {@code maxMatches} is <= zero. * @exception IllegalStateException * if {@code maxMatches} is exceeded. */ public Set keySet(final Pattern query, final int maxMatches) throws IllegalArgumentException, IllegalStateException { final Set retval = new HashSet<>(); if (mRoot != null) { final Set> entries = entrySet(query, maxMatches); // Put the keys into the key set return value. entries.stream() .forEach( entry -> retval.add(entry.getKey())); } return (retval); } // end of keySet(Pattern, int) /** * Returns a collection of all the trees values. This * collection is not backed by the tree. Changes * to the returned collection are not reflected in the tree * and changes to the tree are not reflected in the * collection. * @return a collection of all the trees values. */ @Override public Collection values() { final Collection retval = new ArrayList<>(); if (mRoot != null) { final Set> entries = entrySet(); // Put the values into the key set return value. entries.stream(). forEach((entry) -> { retval.add(entry.getValue()); }); } return (retval); } // end of values() /** * Returns a non-{@code null} collection of all trees values * with keys matching the given pattern. This collection is * not backed by the tree. Changes to the returned * collection are not reflected in the tree and changes to * the tree are not reflected in the collection. * @param query match against this query. * @return a collection of all the trees values matching * {@code query}. */ public Collection values(final Pattern query) { final Collection retval = new LinkedList<>(); if (mRoot != null) { final Set> entries = entrySet(query); // Put the values into the key set return value. entries.stream() .forEach( entry -> retval.add(entry.getValue())); } return (retval); } // end of values(Pattern) /** * Returns a collection of at most {@code maxMatches} values * whose keys match the given pattern. This collection is * not backed by the tree. Changes to the returned * collection are not reflected in the tree and changes to * the tree are not reflected in the collection. * @param query Match against this query. * @param maxMatches Match at most this many keys. * @return a collection of matching keys' values. * @exception IllegalArgumentException * if {@code maxMatches} is <= zero. * @exception IllegalStateException * if {@code maxMatches} is exceeded. */ public Collection values(final Pattern query, final int maxMatches) throws IllegalArgumentException, IllegalStateException { final Collection retval = new ArrayList<>(); if (mRoot != null) { final Set> entries = entrySet(query, maxMatches); // Put the values into the key set return value. entries.stream() .forEach( entry -> retval.add(entry.getValue())); } return (retval); } // end of values(Pattern, int) /** * Returns the set of all key-value mappings. If this tree is * empty, then an empty set is returned. The returned set is * not backed by the tree. Changes to the returned set * or to this tree are not reflected in the other. * @return the set of all key-value mappings. */ @Override public Set> entrySet() { final Set> retval = new HashSet<>(); if (mRoot != null) { entries(mRoot, retval); } return (retval); } // end of entrySet /** * Returns the set of all key-value mappings whose keys match * the given query. If this tree is empty, then an empty set * is returned. The returned set is not backed by the * tree. Changes to the returned set or to this tree are not * reflected in the other. * @param query Match against this query. * @return the set of all key-value mappings. */ public Set> entrySet( final Pattern query) { final Set> retval = new HashSet<>(); if (mRoot != null) { final Component[] components = query.components(); final Queue> queue = new LinkedList<>(); // Place the tree's root on the search queue. queue.add(new TSTSearch<>(mRoot, 0, 0)); entries( components, queue, retval, Integer.MAX_VALUE); } return (retval); } // end of entrySet(Pattern) /** * Returns the set of at most {@code maxMatches} key-value * mappings whose keys match the given query. If this tree is * empty, then an empty set is returned. The returned set is * not backed by the tree. Changes to the returned set * or to this tree are not reflected in the other. * @param query Match against this query. * @param maxMatches Match at most this many keys. * @return the set of at most {@code maxMatches} key-value * mappings. * Throws IllegalArgumentException * if {@code maxMatches} is <= zero. */ public Set> entrySet( final Pattern query, final int maxMatches) { final Set> retval = new HashSet<>(); if (maxMatches <= 0) { throw ( new IllegalArgumentException( "maxMatches <= 0 (" + Integer.toString(maxMatches) + ")")); } else if (mRoot != null) { final Component[] components = query.components(); final Queue> queue = new LinkedList<>(); // Place the tree's root on the search queue. queue.add(new TSTSearch<>(mRoot, 0, 0)); entries(components, queue, retval, maxMatches); } return (retval); } // end of entrySet(Pattern, int) // // end of Map Interface Implementation. //----------------------------------------------------------- /** * Returns the keys which are within a specified Hamming * distance of character sequence {@code s}. The Hamming * distance between two strings of equal length is the * number of positions at which corresponding characters are * different. One string may be transformed into the other by * changing the characters at these positions to the other * strings values. The Hamming distance may be thought of as * the number of errors in one string. *

* If this ternary search tree contains a dictionary, then * this method may be used to find possible correct spellings * for a misspelled word. *

* @param s find the keys within the specified Hamming * distance to this character sequence. * @param distance the desired Hamming distance. * @return the keys which are within a specified Hamming * distance of character sequence {@code s}. If no such keys * are found, then returns an empty collection. */ public Collection nearSearch( final CharSequence s, final int distance) { final int maxIndex = (s.length() - 1); final Queue> queue = new LinkedList<>(); TSTSearch searchNode; TSTNode node; TSTNode child; int index; int d; char c; char splitChar; final Collection retval = new ArrayList<>(); // Instead of using recursion, loop over a search queue // until all possible near entries are found. // Start things off at the root. // Note: the Hamming distance is stored as the regular // expression match count. queue.add(new TSTSearch<>(mRoot, 0, distance)); while ((searchNode = queue.poll()) != null) { node = searchNode.node(); index = searchNode.index(); d = searchNode.matchCount(); c = s.charAt(index); splitChar = node.splitChar(); // Because this is a search, we need to traverse // the tree left, center and right looking for // near keys. // Continue searching as long as we are within the // Hamming distance or the current character would // be found in the left subtree. if ((d > 0 || c < splitChar) && (child = node.child(LEFT)) != null) { queue.add( new TSTSearch<>(child, index, d)); } // The center child cases are dependent on either the // Hamming distance > zero or the current character // matching the node split character. If this is // not the case then skip to the right subtree case. if (d > 0 || c == splitChar) { // If this is a key node and its length equals // the search string, then add the key string to // the return value. if (node.isKey() == true && index == maxIndex) { retval.add(node.key()); } // If the end of the input string has not yet been // reached AND // either we are withing the Hamming distance or // the current character matches the split // character THEN // continue searching down the center else if (index < maxIndex && (child = node.child(CENTER)) != null) { // If the current character does not match // the current character, then we are one // step closer to the Hamming distance. queue.add( new TSTSearch<>( child, (index + 1), (c == splitChar ? d : (d - 1)))); } } if ((d > 0 || c > splitChar) && (child = node.child(RIGHT)) != null) { queue.add(new TSTSearch<>(child, index, d)); } } return (retval); } // end of nearSearch(CharSequence, int) /** * Returns the node associated with the literal text key. * @param key find the node associated with this key. * @return the node associated with the literal text key. */ private TSTNode findNode(final CharSequence key) { TSTNode node; TSTNode node2 = null; int index; final int length = key.length(); int child; // Note: TSTNode.split returns values -1, 0, +1 but // TSTNode.child() expects values 0, 1, and 2. So // convert by adding one to the child value. // Since TSTNode.split returns values -1, 0, +1, // change all to either to -1 or 0 and then add 1 // to the result and then to the index. This // means that if zero is returned, index is // incremented without doing an if comparison. for (node = mRoot, index = 0; node != null && index < length; index += ((Math.abs(child) * -1) + 1), node2 = node, node = node.child(child + 1)) { child = node.split(key.charAt(index)); } // Return the next to last node. return (index == length ? node2 : null); } // end of findNode(CharSequence) /** * Collects all entries in lexicographically ascending order. * @param node start at this node. * @param m the collected entry set. */ private void entries(final TSTNode node, final Set> m) { if (node != null) { // If node is a key, then add its key to the list. if (node.isKey() == true) { m.add(new TSTEntry<>(node)); } // Now continue with the children: left, center and // right. entries(node.child(LEFT), m); entries(node.child(CENTER), m); entries(node.child(RIGHT), m); } return; } // end of entries(TSTNode<>, Set<>) /** * Performs the actual entry set collection work based on a * search pattern. * @param components the search components. * @param queue the search entry queue. * @param m the matched entries. * @param maxMatches the matched entry set maximum allowed * size. */ private void entries(final Component[] components, final Queue> queue, final Set> m, final int maxMatches) { final int length = components.length; TSTSearch searchNode; TSTNode node; int index; int matchCount; char splitChar; int minSize; int maxSize; int nextIndex; TSTNode nextNode; int nextMatchCount; int i; int minRemaining; int mSize = 0; while ((searchNode = queue.poll()) != null && mSize < maxMatches) { node = searchNode.node(); index = searchNode.index(); matchCount = searchNode.matchCount(); splitChar = node.splitChar(); // If the RE component is less than the current // node's split character and there is a left child // node, then continue searching down the left // subtree. // Note: a component may be <, == AND > the SAME // split character. So do not use if, else if, else // but if, if and if. if (components[index].lessThan(splitChar) == true && (nextNode = node.child(LEFT)) != null) { queue.add( new TSTSearch<>(nextNode, index, matchCount)); } // If the RE component matches the current node's // split character or accepts zero characters, then // continue searching down the center subtree. if (components[index].equalTo(splitChar) == true || components[index].minimumSize() == 0) { minSize = components[index].minimumSize(); maxSize = components[index].maximumSize(); nextIndex = (index + 1); nextNode = node.child(CENTER); nextMatchCount = (matchCount + 1); // This node is a matching key if: // 1. This node is a key. // 2. This node satisfies this node's minimum // match size. // 3. The minimum number of characters that // need to be matched by the remaining // components is 0. // This is necessary when the pattern ends // with a component of zero minimum size. if (node.isKey() == true && nextMatchCount >= minSize) { minRemaining = 0; for (i = nextIndex; i < length; ++i) { minRemaining += components[i].minimumSize(); } if (minRemaining == 0) { ++mSize; m.add(new TSTEntry<>(node)); } } // There are three cases to check: // 1. Same component, next node. // If the component is not fully satisfied by // the node, then remain at this component // and move to the next node to see if that // satisfies component. // // 2. Next component, next node. // The component is satisfied by this node. // Move on to the next. // // 3. Next component, same node. // The component is more than satisfied by // this node. So move on to the next component // and see if it can also consume this node. // // Same component, same node is not a case // because that may cause an infinite loop. // The minimum number of matches must occur for // this component to be satisfied. If we are not // at the minimum or the maximum, then continue // matching this component. if ((nextMatchCount < maxSize || maxSize == Component.NO_MAX_MATCH_LIMIT) && nextNode != null) { queue.add( new TSTSearch<>(nextNode, index, nextMatchCount)); } // Have we minimally matched the component? // Is there a next component? if (nextMatchCount >= minSize && nextIndex < length && nextNode != null) { // Move to the next node and the next // regular expression component. queue.add( new TSTSearch<>(nextNode, nextIndex, 0)); } // If this component is at least minimally // satisfied, then move to the next component // but not the next node. Let the next component // consume this node. if (nextMatchCount > minSize && nextIndex < length) { queue.add( new TSTSearch<>(node, nextIndex, 0)); } } // If the component is greater than the current // node's split character, then continue searching // down the right subtree. if (components[index].greaterThan(splitChar) && (nextNode = node.child(RIGHT)) != null) { queue.add( new TSTSearch<>(nextNode, index, matchCount)); } } return; } // end of entries(...) //--------------------------------------------------------------- // Inner classes. // /** * Ternary search trees are built out of TSTNodes. This node * stores the: *
    *
  1. * comparison character, *
  2. *
  3. * the left, center and right node references, *
  4. *
  5. * a flag denoting if this node represents a key, *
  6. *
  7. * the key text and *
  8. *
  9. * the mapped value. *
  10. *
*/ private static final class TSTNode implements Serializable { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Constants. // /** * Serialization version identifier. */ private static final long serialVersionUID = 0x050200L; //------------------------------------------------------- // Locals. // // The following data members are transient because only // the key nodes are serialized (and in sorted order). // Upon deserialization the TST map will be regenerated // from the keys. /** * Character used to decide which node to visit next. */ private final char mSplitChar; /** * The children nodes. The left node is for a character * < the split character, the center node is for a * character equal to the split character and the * right node is for a character > the split * character. */ @SuppressWarnings("rawtypes") private final TSTNode[] mChildren; /** * This flag is true if this is a final node in a * key. */ private boolean mKeyFlag; /** * If this is a key node, then this is the key. */ private CharSequence mKey; /** * If this is a key node, then this is the associated * value. */ private V mValue; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Constructs a TST node with the given split character * and parent node. There are no children initially. * @param c the node split character. */ @SuppressWarnings("rawtypes") public TSTNode(final char c) { mSplitChar = c; mChildren = new TSTNode[CHILD_COUNT]; mKeyFlag = false; mValue = null; } // end of TSTNode(char) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // /** * Returns the node split character. * @return the node split character. */ public char splitChar() { return (mSplitChar); } // end of splitChar() /** * Returns the node's left, center or right child. * @param index left, center or right child index. * @return the node's left, center or right child. */ @SuppressWarnings("unchecked") public TSTNode child(final int index) { return (mChildren[index]); } // end of child(int) /** * Returns {@code true} if this node is the final node * in a map key and {@code false} otherwise. * @return {@code true} if this node is the final node * in a map key and {@code false} otherwise. */ public boolean isKey() { return (mKeyFlag); } // end of isKey() /** * Returns the character sequence for this map's key * if this is the final node of a map key, If this is not * a final node, then returns {@code null}. * @return the character sequence for this map's key * if this is the final node of a map key, */ public CharSequence key() { return (mKey); } // end of key() /** * Returns the node's value if this is a key node. * Otherwise returns {@code null}. * @return the node's value if this is a key node. */ public V value() { return (mValue); } // end of value() /** * Returns {@code true} if this node or any of its * children contains {@code value} and {@code false} if * the {@code value} is not found. Recursively descends * the tree until either the value is found or the tree * is exhausted. * @param value search for this value in the tree. * @return {@code true} if this node or any of its * children contains {@code value} and {@code false} if * the {@code value} is not found. */ public boolean containsValue(final Object value) { boolean retcode = (mKeyFlag == true && equalObjects(mValue, value) == true); if (retcode == false) { int index; for (index = 0; index < mChildren.length && retcode == false; ++index) { if (mChildren[index] != null) { retcode = mChildren[index].containsValue( value); } } } return (retcode); } // end of containsValue(Object) // // end of Get methods. //------------------------------------------------------- //------------------------------------------------------- // Set methods. // /** * Sets the left, center or right child. * @param index left, center or right child index. * @param node child node. */ public void child(final int index, final TSTNode node) { mChildren[index] = node; return; } // end of child(int, TSTNode) /** * Stores the flag and key character sequence. This * is used to remove a key by setting the key flag to * {@code false} and setting the key sequence to * {@code null}. * @param flag {@code true} if this node is a key final * node and {@code false} otherwise. * @param key the key character sequence. */ public void key(final boolean flag, final CharSequence key) { mKeyFlag = flag; mKey = key; return; } // end of key(boolean, CharSequence) /** * Sets the value associated with a key and returns the * previously stored value. May return {@code null}. * @param value the key's associated value. * @return the previously stored value. */ public V value(final V value) { final V retval = mValue; mValue = value; return (retval); } // end of value(V) // // end of Set methods. //------------------------------------------------------- /** * Returns an integer value {@link #LEFT}, * {@link #CENTER} or {@link #RIGHT} depending on whether * {@code c} is the left, center or right child node. * @param c compare this character against the split * character. * @return an integer value {@link #LEFT}, * {@link #CENTER} or {@link #RIGHT} depending on whether * {@code c} is the left, center or right child node. */ public int split(final char c) { int index = (c - mSplitChar); if (index != 0) { index /= Math.abs(index); } return (index); } // end of split(char) /** * Recursively clears its child nodes and then its own * children list. */ public void clear() { int index; for (index = 0; index < mChildren.length; ++index) { if (mChildren[index] != null) { mChildren[index].clear(); mChildren[index] = null; } } return; } // end of clear() /** * Returns {@code true} if {@code o1} and {@code o2} are * equal and {@code false} otherwise. The objects are * equal if: *
    *
  • * If both {@code o1} and {@code o2} are {@code null} * or *
  • *
  • * If neither is {@code null} and * {@link java.lang.Object#equals(java.lang.Object)} * returns {@code true} *
  • *
* @param o1 the first object. * @param o2 the second object. * @return {@code true} if {@code o1} and {@code o2} are * equal and {@code false} otherwise. */ private static boolean equalObjects(final Object o1, final Object o2) { return ((o1 == null && o2 == null) || (o1 != null && o2 != null && o1.equals(o2) == true)); } // end of equalObjects(Object, Object) } // end of class TSTNode /** * TSTEntry is used to place key-value entries into an * entry set. */ private static final class TSTEntry implements Map.Entry { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * This entry's associated node. */ private final TSTNode mNode; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * This entry references the given key node. * @param node a key node. * @exception IllegalArgumentException * if {@code node} is not a key. */ public TSTEntry(final TSTNode node) throws IllegalArgumentException { assert (node.isKey() == true) : node; mNode = node; } // end of TSTEntry(TSTNode) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the entry's key. * @return the entry's key. */ @Override public CharSequence getKey() { return (mNode.mKey); } // end of getKey() /** * Returns the entry's value. * @return the entry's value. */ @Override public V getValue() { return (mNode.mValue); } // end of getValue() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * This operation is unsupported because the entry set * is not backed by the ternary search tree. * @exception UnsupportedOperationException * because this operation is not supported. */ @Override public V setValue(final V o) { throw (new UnsupportedOperationException()); } // end of setValue(V) // // end of Set Methods. //------------------------------------------------------- /** * Returns {@code true} if {@code o} is a * non-{@code null TSTEntry} instance referencing the * same ternary search tree node and {@code false} * otherwise. * @param o object to be compore with this map entry. * @return {@code true} if {@code o} is a * non-{@code null TSTEntry} instance referencing the * same ternary search tree node */ @Override public boolean equals(final Object o) { boolean retcode = (this == o); if (retcode == false && o instanceof TSTEntry) { @SuppressWarnings ("unchecked") final TSTEntry entry = (TSTEntry) o; // TSTNodes are referentially unique. retcode = (mNode == entry.mNode); } return (retcode); } // end of equals(Object) /** * Returns the node's hash code. * @return the node's hash code. */ @Override public int hashCode() { return (mNode.hashCode()); } // end of hashCode() /** * Returns a {@code String} representation of this map * entry. The string has the format of: *
         *   
         * "<key>"=<value>
         *   
         * 
* @return a {@code String} representation of this map * entry. */ @Override public String toString() { return ( String.format( "\"%s\"=%s", mNode.mKey, mNode.mValue)); } // end of toString() } // end of class TSTEntry /** * This class is used to store the search information on a * queue. This allows the search to be done iteratively * rather than recursively. * @param the mapped value type. */ private static final class TSTSearch { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Continue the search at this node. */ private final TSTNode mNode; /** * Continue the search at this regular expression * component. */ private final int mIndex; /** * Continue the search with this regular expression * component match count. */ private final int mMatchCount; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Stores a point in a search iteration. This point * rests on a specified node and index into the regular * expression component array, and the current RE * component's match count. An RE component may need to * match multiple nodes in order to be statisfied. * @param node the search is at this node. * @param index the RE component index. * @param matchCount how much of the RE component has * been matched to this point. */ public TSTSearch(final TSTNode node, final int index, final int matchCount) { mNode = node; mIndex = index; mMatchCount = matchCount; } // end of TSTSearch(TSTNode, int, int) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the next node to search. * @return the next node to search. */ public TSTNode node() { return (mNode); } // end of node() /** * Returns the regular expression component index. * @return the regular expression component index. */ public int index() { return (mIndex); } // end of index() /** * Returns the regular expression component's match * count. * @return the regular expression component's match * count. */ public int matchCount() { return (mMatchCount); } // end of matchCount() // // end of Get Methods. //------------------------------------------------------- } // end of class TSTSearch } // end of class TernarySearchTree // // CHANGE LOG // $Log: TernarySearchTree.java,v $ // Revision 1.13 2008/02/14 17:35:26 charlesr // Corrected errors found by JUnit tests. // // Revision 1.12 2007/08/13 18:24:22 charlesr // Forget to place value into pre-existing node. // // Revision 1.11 2007/06/16 14:35:31 charlesr // Added match limit to pattern matching. // // Revision 1.10 2007/02/25 20:09:20 charlesr // Converted pattern lookup from recursive to iterative. // Added inner class TSTSearch. // // Revision 1.9 2007/02/25 19:17:34 charlesr // Added key to TSTNode. Removed word parameter from // entries() pattern search method. // // Revision 1.8 2007/02/23 13:38:27 charlesr // Corrected javadoc comments. // // Revision 1.7 2006/10/21 18:10:52 charlesr // Added generic interface. // // Revision 1.6 2006/10/21 16:37:13 charlesr // Renamed TSTMap to TernarySearchTree. // Using net.sf.eBus.util.regex.Pattern for queries. // Pushed class closer to java.util.Map interface. // // Revision 1.5 2006/10/16 15:47:49 charlesr // Corrected Kleene closure match. // // Revision 1.4 2006/10/16 14:02:13 charlesr // Added feature to associate a value with the key. // Changed search() to containsKey(). // Changed insert() to put(). // Added get() method. // Added Kleene closure wildcard. // // Revision 1.3 2006/10/14 19:28:54 charlesr // Added particial match feature. // // Revision 1.2 2006/10/14 18:51:35 charlesr // Rewrote according to Bentley, Sedgewick papper. // // Revision 1.1 2004/07/25 16:02:39 charlesr // Corrected javadoc comments. // // Revision 1.0 2004/07/19 15:23:15 charlesr // Initial revision //




© 2015 - 2024 Weber Informatics LLC | Privacy Policy