Home | History | Annotate | Download | only in tree
      1 /*
      2 The MIT License
      3 
      4 Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
      5 
      6 http://code.google.com/p/radixtree/
      7 
      8 Permission is hereby granted, free of charge, to any person obtaining a copy
      9 of this software and associated documentation files (the "Software"), to deal
     10 in the Software without restriction, including without limitation the rights
     11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     12 copies of the Software, and to permit persons to whom the Software is
     13 furnished to do so, subject to the following conditions:
     14 
     15 The above copyright notice and this permission notice shall be included in
     16 all copies or substantial portions of the Software.
     17 
     18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     24 THE SOFTWARE.
     25 */
     26 
     27 package ds.tree;
     28 
     29 import java.util.ArrayList;
     30 import java.util.Formattable;
     31 import java.util.Formatter;
     32 import java.util.Iterator;
     33 import java.util.LinkedList;
     34 import java.util.Queue;
     35 
     36 /**
     37  * Implementation for Radix tree {@link RadixTree}
     38  *
     39  * @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
     40  * @author Javid Jamae
     41  * @author Dennis Heidsiek
     42  */
     43 public class RadixTreeImpl<T> implements RadixTree<T>, Formattable {
     44 
     45     protected RadixTreeNode<T> root;
     46 
     47     protected long size;
     48 
     49     /**
     50      * Create a Radix Tree with only the default node root.
     51      */
     52     public RadixTreeImpl() {
     53         root = new RadixTreeNode<T>();
     54         root.setKey("");
     55         size = 0;
     56     }
     57 
     58     public T find(String key) {
     59         Visitor<T,T> visitor = new VisitorImpl<T,T>() {
     60 
     61             public void visit(String key, RadixTreeNode<T> parent,
     62                     RadixTreeNode<T> node) {
     63                 if (node.isReal())
     64                     result = node.getValue();
     65             }
     66         };
     67 
     68         visit(key, visitor);
     69 
     70         return visitor.getResult();
     71     }
     72 
     73     public boolean replace(String key, final T value) {
     74         Visitor<T,T> visitor = new VisitorImpl<T,T>() {
     75             public void visit(String key, RadixTreeNode<T> parent, RadixTreeNode<T> node) {
     76                 if (node.isReal()) {
     77                     node.setValue(value);
     78                     result = value;
     79                 } else {
     80                     result = null;
     81                 }
     82             }
     83         };
     84 
     85         visit(key, visitor);
     86 
     87         return visitor.getResult() != null;
     88     }
     89 
     90     public boolean delete(String key) {
     91         Visitor<T, Boolean> visitor = new VisitorImpl<T, Boolean>(Boolean.FALSE) {
     92             public void visit(String key, RadixTreeNode<T> parent,
     93                     RadixTreeNode<T> node) {
     94                 result = node.isReal();
     95 
     96                 // if it is a real node
     97                 if (result) {
     98                     // If there no children of the node we need to
     99                     // delete it from the its parent children list
    100                     if (node.getChildern().size() == 0) {
    101                         Iterator<RadixTreeNode<T>> it = parent.getChildern()
    102                                 .iterator();
    103                         while (it.hasNext()) {
    104                             if (it.next().getKey().equals(node.getKey())) {
    105                                 it.remove();
    106                                 break;
    107                             }
    108                         }
    109 
    110                         // if parent is not real node and has only one child
    111                         // then they need to be merged.
    112                         if (parent.getChildern().size() == 1
    113                                 && parent.isReal() == false) {
    114                             mergeNodes(parent, parent.getChildern().get(0));
    115                         }
    116                     } else if (node.getChildern().size() == 1) {
    117                         // we need to merge the only child of this node with
    118                         // itself
    119                         mergeNodes(node, node.getChildern().get(0));
    120                     } else { // we jus need to mark the node as non real.
    121                         node.setReal(false);
    122                     }
    123                 }
    124             }
    125 
    126             /**
    127              * Merge a child into its parent node. Operation only valid if it is
    128              * only child of the parent node and parent node is not a real node.
    129              *
    130              * @param parent
    131              *            The parent Node
    132              * @param child
    133              *            The child Node
    134              */
    135             private void mergeNodes(RadixTreeNode<T> parent,
    136                     RadixTreeNode<T> child) {
    137                 parent.setKey(parent.getKey() + child.getKey());
    138                 parent.setReal(child.isReal());
    139                 parent.setValue(child.getValue());
    140                 parent.setChildern(child.getChildern());
    141             }
    142 
    143         };
    144 
    145         visit(key, visitor);
    146 
    147         if(visitor.getResult()) {
    148             size--;
    149         }
    150         return visitor.getResult().booleanValue();
    151     }
    152 
    153     /*
    154      * (non-Javadoc)
    155      * @see ds.tree.RadixTree#insert(java.lang.String, java.lang.Object)
    156      */
    157     public void insert(String key, T value) throws DuplicateKeyException {
    158         try {
    159 			insert(key, root, value);
    160 		} catch (DuplicateKeyException e) {
    161 			// re-throw the exception with 'key' in the message
    162 			throw new DuplicateKeyException("Duplicate key: '" + key + "'");
    163 		}
    164         size++;
    165     }
    166 
    167     /**
    168      * Recursively insert the key in the radix tree.
    169      *
    170      * @param key The key to be inserted
    171      * @param node The current node
    172      * @param value The value associated with the key
    173      * @throws DuplicateKeyException If the key already exists in the database.
    174      */
    175     private void insert(String key, RadixTreeNode<T> node, T value)
    176             throws DuplicateKeyException {
    177 
    178         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
    179 
    180         // we are either at the root node
    181         // or we need to go down the tree
    182         if (node.getKey().equals("") == true || numberOfMatchingCharacters == 0 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
    183             boolean flag = false;
    184             String newText = key.substring(numberOfMatchingCharacters, key.length());
    185             for (RadixTreeNode<T> child : node.getChildern()) {
    186                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
    187                     flag = true;
    188                     insert(newText, child, value);
    189                     break;
    190                 }
    191             }
    192 
    193             // just add the node as the child of the current node
    194             if (flag == false) {
    195                 RadixTreeNode<T> n = new RadixTreeNode<T>();
    196                 n.setKey(newText);
    197                 n.setReal(true);
    198                 n.setValue(value);
    199 
    200                 node.getChildern().add(n);
    201             }
    202         }
    203         // there is a exact match just make the current node as data node
    204         else if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters == node.getKey().length()) {
    205             if (node.isReal() == true) {
    206                 throw new DuplicateKeyException("Duplicate key");
    207             }
    208 
    209             node.setReal(true);
    210             node.setValue(value);
    211         }
    212         // This node need to be split as the key to be inserted
    213         // is a prefix of the current node key
    214         else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey().length()) {
    215             RadixTreeNode<T> n1 = new RadixTreeNode<T>();
    216             n1.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
    217             n1.setReal(node.isReal());
    218             n1.setValue(node.getValue());
    219             n1.setChildern(node.getChildern());
    220 
    221             node.setKey(key.substring(0, numberOfMatchingCharacters));
    222             node.setReal(false);
    223             node.setChildern(new ArrayList<RadixTreeNode<T>>());
    224             node.getChildern().add(n1);
    225 
    226             if(numberOfMatchingCharacters < key.length()) {
    227 	            RadixTreeNode<T> n2 = new RadixTreeNode<T>();
    228 	            n2.setKey(key.substring(numberOfMatchingCharacters, key.length()));
    229 	            n2.setReal(true);
    230 	            n2.setValue(value);
    231 
    232 	            node.getChildern().add(n2);
    233             } else {
    234                 node.setValue(value);
    235                 node.setReal(true);
    236             }
    237         }
    238         // this key need to be added as the child of the current node
    239         else {
    240             RadixTreeNode<T> n = new RadixTreeNode<T>();
    241             n.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
    242             n.setChildern(node.getChildern());
    243             n.setReal(node.isReal());
    244             n.setValue(node.getValue());
    245 
    246             node.setKey(key);
    247             node.setReal(true);
    248             node.setValue(value);
    249 
    250             node.getChildern().add(n);
    251         }
    252     }
    253 
    254     public ArrayList<T> searchPrefix(String key, int recordLimit) {
    255         ArrayList<T> keys = new ArrayList<T>();
    256 
    257         RadixTreeNode<T> node = searchPefix(key, root);
    258 
    259         if (node != null) {
    260             if (node.isReal()) {
    261                 keys.add(node.getValue());
    262             }
    263             getNodes(node, keys, recordLimit);
    264         }
    265 
    266         return keys;
    267     }
    268 
    269     private void getNodes(RadixTreeNode<T> parent, ArrayList<T> keys, int limit) {
    270         Queue<RadixTreeNode<T>> queue = new LinkedList<RadixTreeNode<T>>();
    271 
    272         queue.addAll(parent.getChildern());
    273 
    274         while (!queue.isEmpty()) {
    275             RadixTreeNode<T> node = queue.remove();
    276             if (node.isReal() == true) {
    277                 keys.add(node.getValue());
    278             }
    279 
    280             if (keys.size() == limit) {
    281                 break;
    282             }
    283 
    284             queue.addAll(node.getChildern());
    285         }
    286     }
    287 
    288     private RadixTreeNode<T> searchPefix(String key, RadixTreeNode<T> node) {
    289         RadixTreeNode<T> result = null;
    290 
    291         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
    292 
    293         if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters <= node.getKey().length()) {
    294             result = node;
    295         } else if (node.getKey().equals("") == true
    296                 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
    297             String newText = key.substring(numberOfMatchingCharacters, key.length());
    298             for (RadixTreeNode<T> child : node.getChildern()) {
    299                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
    300                     result = searchPefix(newText, child);
    301                     break;
    302                 }
    303             }
    304         }
    305 
    306         return result;
    307     }
    308 
    309     public boolean contains(String key) {
    310         Visitor<T, Boolean> visitor = new VisitorImpl<T,Boolean>(Boolean.FALSE) {
    311             public void visit(String key, RadixTreeNode<T> parent,
    312                     RadixTreeNode<T> node) {
    313                 result = node.isReal();
    314             }
    315         };
    316 
    317         visit(key, visitor);
    318 
    319         return visitor.getResult().booleanValue();
    320     }
    321 
    322     /**
    323      * visit the node those key matches the given key
    324      * @param key The key that need to be visited
    325      * @param visitor The visitor object
    326      */
    327     public <R> void visit(String key, Visitor<T, R> visitor) {
    328         if (root != null) {
    329             visit(key, visitor, null, root);
    330         }
    331     }
    332 
    333     /**
    334      * recursively visit the tree based on the supplied "key". calls the Visitor
    335      * for the node those key matches the given prefix
    336      *
    337      * @param prefix
    338      *            The key o prefix to search in the tree
    339      * @param visitor
    340      *            The Visitor that will be called if a node with "key" as its
    341      *            key is found
    342      * @param node
    343      *            The Node from where onward to search
    344      */
    345     private <R> void visit(String prefix, Visitor<T, R> visitor,
    346             RadixTreeNode<T> parent, RadixTreeNode<T> node) {
    347 
    348         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix);
    349 
    350         // if the node key and prefix match, we found a match!
    351         if (numberOfMatchingCharacters == prefix.length() && numberOfMatchingCharacters == node.getKey().length()) {
    352             visitor.visit(prefix, parent, node);
    353         } else if (node.getKey().equals("") == true // either we are at the
    354                 // root
    355                 || (numberOfMatchingCharacters < prefix.length() && numberOfMatchingCharacters >= node.getKey().length())) { // OR we need to
    356             // traverse the childern
    357             String newText = prefix.substring(numberOfMatchingCharacters, prefix.length());
    358             for (RadixTreeNode<T> child : node.getChildern()) {
    359                 // recursively search the child nodes
    360                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
    361                     visit(newText, visitor, node, child);
    362                     break;
    363                 }
    364             }
    365         }
    366     }
    367 
    368     public long getSize() {
    369         return size;
    370     }
    371 
    372     /**
    373      * Display the Trie on console.
    374      *
    375      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
    376      * @see formatTo
    377      */
    378     @Deprecated
    379     public void display() {
    380         formatNodeTo(new Formatter(System.out), 0, root);
    381     }
    382 
    383     @Deprecated
    384     private void display(int level, RadixTreeNode<T> node) {
    385         formatNodeTo(new Formatter(System.out), level, node);
    386     }
    387 
    388     /**
    389      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
    390      */
    391     private void formatNodeTo(Formatter f, int level, RadixTreeNode<T> node) {
    392         for (int i = 0; i < level; i++) {
    393             f.format(" ");
    394         }
    395         f.format("|");
    396         for (int i = 0; i < level; i++) {
    397             f.format("-");
    398         }
    399 
    400         if (node.isReal() == true)
    401             f.format("%s[%s]*%n", node.getKey(),  node.getValue());
    402         else
    403             f.format("%s%n", node.getKey());
    404 
    405         for (RadixTreeNode<T> child : node.getChildern()) {
    406             formatNodeTo(f, level + 1, child);
    407         }
    408 	}
    409 
    410 	/**
    411 	 * Writes a textual representation of this tree to the given formatter.
    412 	 *
    413 	 * Currently, all options are simply ignored.
    414 	 *
    415      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
    416 	 */
    417 	public void formatTo(Formatter formatter, int flags, int width, int precision) {
    418 		formatNodeTo(formatter, 0, root);
    419 	}
    420 
    421     /**
    422      * Complete the a prefix to the point where ambiguity starts.
    423      *
    424      *  Example:
    425      *  If a tree contain "blah1", "blah2"
    426      *  complete("b") -> return "blah"
    427      *
    428      * @param prefix The prefix we want to complete
    429      * @return The unambiguous completion of the string.
    430      */
    431 	public String complete(String prefix) {
    432 		return complete(prefix, root, "");
    433 	}
    434 
    435 	private String complete(String key, RadixTreeNode<T> node, String base) {
    436         int i = 0;
    437         int keylen = key.length();
    438         int nodelen = node.getKey().length();
    439 
    440         while (i < keylen && i < nodelen) {
    441             if (key.charAt(i) != node.getKey().charAt(i)) {
    442                 break;
    443             }
    444             i++;
    445         }
    446 
    447         if (i == keylen && i <= nodelen) {
    448             return base + node.getKey();
    449         }
    450         else if (nodelen == 0 || (i < keylen && i >= nodelen)) {
    451             String beginning = key.substring(0, i);
    452             String ending = key.substring(i, keylen);
    453             for (RadixTreeNode<T> child : node.getChildern()) {
    454                 if (child.getKey().startsWith(ending.charAt(0) + "")) {
    455                     return complete(ending, child, base + beginning);
    456                 }
    457             }
    458         }
    459 
    460         return "";
    461     }
    462 }