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 */ RadixTreeImpl()52 public RadixTreeImpl() { 53 root = new RadixTreeNode<T>(); 54 root.setKey(""); 55 size = 0; 56 } 57 find(String key)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 replace(String key, final T value)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 delete(String key)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 */ insert(String key, T value)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 */ insert(String key, RadixTreeNode<T> node, T value)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 searchPrefix(String key, int recordLimit)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 getNodes(RadixTreeNode<T> parent, ArrayList<T> keys, int limit)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 searchPefix(String key, RadixTreeNode<T> node)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 contains(String key)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 */ visit(String key, Visitor<T, R> visitor)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 */ visit(String prefix, Visitor<T, R> visitor, RadixTreeNode<T> parent, RadixTreeNode<T> node)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 getSize()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 display()379 public void display() { 380 formatNodeTo(new Formatter(System.out), 0, root); 381 } 382 383 @Deprecated display(int level, RadixTreeNode<T> node)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 */ formatNodeTo(Formatter f, int level, RadixTreeNode<T> node)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 */ formatTo(Formatter formatter, int flags, int width, int precision)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 */ complete(String prefix)431 public String complete(String prefix) { 432 return complete(prefix, root, ""); 433 } 434 complete(String key, RadixTreeNode<T> node, String base)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 }