source: src/main/java/weka/core/Trie.java @ 14

Last change on this file since 14 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 20.5 KB
Line 
1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 * Trie.java
19 * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
20 */
21
22package weka.core;
23
24import java.io.Serializable;
25import java.lang.reflect.Array;
26import java.util.Collection;
27import java.util.Enumeration;
28import java.util.Hashtable;
29import java.util.Iterator;
30import java.util.Vector;
31
32import javax.swing.tree.DefaultMutableTreeNode;
33
34/**
35 * A class representing a Trie data structure for strings.
36 * See also <a href="http://en.wikipedia.org/wiki/Trie" target="_blank">Trie</a>
37 * on WikiPedia.
38 *
39 * @author  fracpete (fracpete at waikato dot ac dot nz)
40 * @version $Revision: 5953 $
41 */
42public class Trie
43  implements Serializable, Cloneable, Collection<String>, RevisionHandler {
44
45  /** for serialization */
46  private static final long serialVersionUID = -5897980928817779048L;
47
48  /**
49   * Represents a node in the trie.
50   *
51   * @author  fracpete (fracpete at waikato dot ac dot nz)
52   * @version $Revision: 5953 $
53   */
54  public static class TrieNode
55    extends DefaultMutableTreeNode
56    implements RevisionHandler {
57   
58    /** for serialization */
59    private static final long serialVersionUID = -2252907099391881148L;
60   
61    /** the stop character */
62    public final static Character STOP = '\0';
63
64    /** for fast access to the children */
65    protected Hashtable<Character,TrieNode> m_Children;
66   
67    /**
68     * initializes the node
69     *
70     * @param c         the value of this node
71     */
72    public TrieNode(char c) {
73      this(new Character(c));
74    }
75   
76    /**
77     * initializes the node
78     *
79     * @param c         the value of this node
80     */
81    public TrieNode(Character c) {
82      super(c);
83     
84      m_Children = new Hashtable<Character,TrieNode>(100);
85    }
86   
87    /**
88     * returns the stored character
89     *
90     * @return          the stored character
91     */
92    public Character getChar() {
93      return (Character) getUserObject();
94    }
95   
96    /**
97     * sets the character this node represents
98     *
99     * @param value     the character to store
100     */
101    public void setChar(Character value) {
102      setUserObject(value);
103    }
104
105    /**
106     * adds the given string to its children (creates children if necessary)
107     *
108     * @param suffix    the suffix to add to its children
109     * @return          true if the add operation changed the structure
110     */
111    public boolean add(String suffix) {
112      boolean           result;
113      Character         c;
114      String            newSuffix;
115      TrieNode          child;
116     
117      result    = false;
118      c         = suffix.charAt(0);
119      newSuffix = suffix.substring(1);
120     
121      // find child and add if necessary
122      child = m_Children.get(c);
123      if (child == null) {
124        result = true;
125        child = add(c);
126      }
127     
128      // propagate remaining suffix
129      if (newSuffix.length() > 0)
130        result = child.add(newSuffix) || result;
131     
132      return result;
133    }
134   
135    /**
136     * adds the given charater to its children
137     *
138     * @param c         the character to add
139     * @return          the generated child node
140     */
141    protected TrieNode add(Character c) {
142      TrieNode  child;
143     
144      child = new TrieNode(c);
145      add(child);
146      m_Children.put(c, child);
147     
148      return child;
149    }
150   
151    /**
152     * removes the given characted from its children
153     *
154     * @param c         the character to remove
155     */
156    protected void remove(Character c) {
157      TrieNode  child;
158     
159      child = m_Children.get(c);
160      remove(child);
161      m_Children.remove(c);
162    }
163   
164    /**
165     * Removes a suffix from the trie.
166     *
167     * @param suffix    the suffix to remove
168     * @return          true if this trie changed as a result of the call
169     */
170    public boolean remove(String suffix) {
171      boolean           result;
172      Character         c;
173      String            newSuffix;
174      TrieNode          child;
175     
176      c         = suffix.charAt(0);
177      newSuffix = suffix.substring(1);
178      child     = m_Children.get(c);
179     
180      if (child == null) {
181        result = false;
182      }
183      else if (newSuffix.length() == 0) {
184        remove(c);
185        result = true;
186      }
187      else {
188        result = child.remove(newSuffix);
189        if (child.getChildCount() == 0)
190          remove(child.getChar());
191      }
192     
193      return result;
194    }
195
196    /**
197     * checks whether a suffix can be found in its children
198     *
199     * @param suffix    the suffix to look for
200     * @return          true if suffix was found
201     */
202    public boolean contains(String suffix) {
203      boolean           result;
204      Character         c;
205      String            newSuffix;
206      TrieNode          child;
207     
208      c         = suffix.charAt(0);
209      newSuffix = suffix.substring(1);
210      child     = m_Children.get(c);
211     
212      if (child == null)
213        result = false;
214      else if (newSuffix.length() == 0)
215        result = true;
216      else
217        result = child.contains(newSuffix);
218
219      return result;
220    }
221   
222    /**
223     * creates a deep copy of itself
224     *
225     * @return          a deep copy of itself
226     */
227    public Object clone() {
228      TrieNode                  result;
229      Enumeration<Character>    keys;
230      Character                 key;
231      TrieNode                  child;
232     
233      result = new TrieNode(getChar());
234      keys   = m_Children.keys();
235      while (keys.hasMoreElements()) {
236        key   = keys.nextElement();
237        child = (TrieNode) m_Children.get(key).clone();
238        result.add(child);
239        result.m_Children.put(key, child);
240      }
241     
242      return result;
243    }
244   
245    /**
246     * Indicates whether some other object is "equal to" this one.
247     *
248     * @param obj       the object to check for equality
249     * @return          true if equal
250     */
251    public boolean equals(Object obj) {
252      boolean                   result;
253      TrieNode                  node;
254      Enumeration<Character>    keys;
255      Character                 key;
256     
257      node   = (TrieNode) obj;
258     
259      // is payload the same?
260      if (getChar() == null)
261        result = (node.getChar() == null);
262      else
263        result = getChar().equals(node.getChar());
264     
265      // check children
266      if (result) {
267        keys = m_Children.keys();
268        while (keys.hasMoreElements()) {
269          key    = keys.nextElement();
270          result = m_Children.get(key).equals(node.m_Children.get(key));
271          if (!result)
272            break;
273        }
274      }
275     
276      return result;
277    }
278
279    /**
280     * returns the node with the given suffix
281     *
282     * @param suffix    the suffix to look for
283     * @return          null if unsuccessful otherwise the corresponding node
284     */
285    public TrieNode find(String suffix) {
286      TrieNode          result;
287      Character         c;
288      String            newSuffix;
289      TrieNode          child;
290     
291      c         = suffix.charAt(0);
292      newSuffix = suffix.substring(1);
293      child     = m_Children.get(c);
294     
295      if (child == null)
296        result = null;
297      else if (newSuffix.length() == 0)
298        result = child;
299      else
300        result = child.find(newSuffix);
301
302      return result;
303    }
304
305    /**
306     * returns the common prefix for all the nodes starting with this node.
307     * The result includes this node, unless it's the root node or a STOP node.
308     *
309     * @return                  the result of the search
310     */
311    public String getCommonPrefix() {
312      return getCommonPrefix("");
313    }
314
315    /**
316     * returns the common prefix for all the nodes starting with the node
317     * for the specified prefix. Can be null if initial prefix is not found.
318     * The result includes this node, unless it's the root node or a STOP node.
319     * Using the empty string means starting with this node.
320     *
321     * @param startPrefix       the prefix of the node to start the search from
322     * @return                  the result of the search, null if startPrefix
323     *                          cannot be found
324     */
325    public String getCommonPrefix(String startPrefix) {
326      String    result;
327      TrieNode  startNode;
328
329      if (startPrefix.length() == 0)
330        startNode = this;
331      else
332        startNode = find(startPrefix);
333     
334      if (startNode == null)
335        result = null;
336      else
337        result = startPrefix + startNode.determineCommonPrefix("");
338       
339      return result;
340    }
341
342    /**
343     * determines the common prefix of the nodes.
344     *
345     * @param currentPrefix     the common prefix found so far
346     * @return                  the result of the search
347     */
348    protected String determineCommonPrefix(String currentPrefix) {
349      String    result;
350      String    newPrefix;
351     
352      if (!isRoot() && (getChar() != STOP))
353        newPrefix = currentPrefix + getChar();
354      else
355        newPrefix = currentPrefix;
356     
357      if (m_Children.size() == 1)
358        result = ((TrieNode) getChildAt(0)).determineCommonPrefix(newPrefix);
359      else
360        result = newPrefix;
361     
362      return result;
363    }
364   
365    /**
366     * returns the number of stored strings, i.e., leaves
367     *
368     * @return          the number of stored strings
369     */
370    public int size() {
371      int       result;
372      TrieNode  leaf;
373     
374      result = 0;
375      leaf   = (TrieNode) getFirstLeaf();
376      while (leaf != null) {
377        if (leaf != getRoot())
378          result++;
379        leaf = (TrieNode) leaf.getNextLeaf();
380      }
381     
382      return result;
383    }
384   
385    /**
386     * returns the full string up to the root
387     *
388     * @return          the full string to the root
389     */
390    public String getString() {
391      char[]    result;
392      TrieNode  node;
393     
394      result = new char[this.getLevel()];
395      node   = this;
396      while (node.getParent() != null) {
397        if (node.isRoot())
398          break;
399        else
400          result[node.getLevel() - 1] = node.getChar();
401        node = (TrieNode) node.getParent();
402      }
403     
404      return new String(result);
405    }
406   
407    /**
408     * returns the node in a string representation
409     *
410     * @return          the node as string
411     */
412    public String toString() {
413      return "" + getChar();
414    }
415   
416    /**
417     * Returns the revision string.
418     *
419     * @return          the revision
420     */
421    public String getRevision() {
422      return RevisionUtils.extract("$Revision: 5953 $");
423    }
424  }
425 
426  /**
427   * Represents an iterator over a trie
428   *
429   * @author  fracpete (fracpete at waikato dot ac dot nz)
430   * @version $Revision: 5953 $
431   */
432  public static class TrieIterator 
433    implements Iterator<String>, RevisionHandler {
434   
435    /** the node to use as root */
436    protected TrieNode m_Root;
437   
438    /** the last leaf for this root node */
439    protected TrieNode m_LastLeaf;
440   
441    /** the current leaf node */
442    protected TrieNode m_CurrentLeaf;
443   
444    /**
445     * initializes the iterator
446     *
447     * @param node              the node to use as root
448     */
449    public TrieIterator(TrieNode node) {
450      super();
451     
452      m_Root        = node;
453      m_CurrentLeaf = (TrieNode) m_Root.getFirstLeaf();
454      m_LastLeaf    = (TrieNode) m_Root.getLastLeaf();
455    }
456   
457    /**
458     * Returns true if the iteration has more elements.
459     *
460     * @return          true if there is at least one more element
461     */
462    public boolean hasNext() {
463      return (m_CurrentLeaf != null);
464    }
465   
466    /**
467     * Returns the next element in the iteration.
468     *
469     * @return          the next element
470     */
471    public String next() {
472      String    result;
473     
474      result        = m_CurrentLeaf.getString();
475      result        = result.substring(0, result.length() - 1);  // remove STOP
476      if (m_CurrentLeaf != m_LastLeaf)
477        m_CurrentLeaf = (TrieNode) m_CurrentLeaf.getNextLeaf();
478      else
479        m_CurrentLeaf = null;
480     
481      return result;
482    }
483   
484    /**
485     * ignored
486     */
487    public void remove() {
488    }
489   
490    /**
491     * Returns the revision string.
492     *
493     * @return          the revision
494     */
495    public String getRevision() {
496      return RevisionUtils.extract("$Revision: 5953 $");
497    }
498  }
499 
500  /** the root node */
501  protected TrieNode m_Root;
502
503  /** the hash code */
504  protected int m_HashCode;
505 
506  /** whether the structure got modified and the hash code needs to be
507   * re-calculated */
508  protected boolean m_RecalcHashCode;
509 
510  /**
511   * initializes the data structure
512   */
513  public Trie() {
514    super();
515   
516    m_Root           = new TrieNode(null);
517    m_RecalcHashCode = true;
518  }
519 
520  /**
521   * Ensures that this collection contains the specified element.
522   *
523   * @param o           the string to add
524   * @return            true if the structure changed
525   */
526  public boolean add(String o) {
527    return m_Root.add(o + TrieNode.STOP);
528  }
529 
530  /**
531   * Adds all of the elements in the specified collection to this collection
532   *
533   * @param c           the collection to add
534   */
535  public boolean addAll(Collection<? extends String> c) {
536    boolean             result;
537    Iterator<? extends String>  iter;
538   
539    result = false;
540   
541    iter = c.iterator();
542    while (iter.hasNext())
543      result = add(iter.next()) || result;
544   
545    return result;
546  }
547 
548  /**
549   * Removes all of the elements from this collection
550   */
551  public void clear() {
552    m_Root.removeAllChildren();
553    m_RecalcHashCode = true;
554  }
555
556  /**
557   * returns a deep copy of itself
558   *
559   * @return            a copy of itself
560   */
561  public Object clone() {
562    Trie        result;
563   
564    result = new Trie();
565    result.m_Root = (TrieNode) m_Root.clone();
566   
567    return result;
568  }
569 
570  /**
571   * Returns true if this collection contains the specified element.
572   *
573   * @param o           the object to check for in trie
574   * @return            true if found
575   */
576  public boolean contains(Object o) {
577    return m_Root.contains(((String) o) + TrieNode.STOP);
578  }
579 
580  /**
581   * Returns true if this collection contains all of the elements in the
582   * specified collection.
583   *
584   * @param c           the collection to look for in the trie
585   * @return            true if all elements were found
586   */
587  public boolean containsAll(Collection<?> c) {
588    boolean     result;
589    Iterator    iter;
590   
591    result = true;
592   
593    iter = c.iterator();
594    while (iter.hasNext()) {
595      if (!contains(iter.next())) {
596        result = false;
597        break;
598      }
599    }
600   
601    return result;
602  }
603 
604  /**
605   * checks whether the given prefix is stored in the trie
606   *
607   * @param prefix      the prefix to check
608   * @return            true if the prefix is part of the trie
609   */
610  public boolean containsPrefix(String prefix) {
611    return m_Root.contains(prefix);
612  }
613 
614  /**
615   * Compares the specified object with this collection for equality.
616   *
617   * @param o           the object to check for equality
618   */
619  public boolean equals(Object o) {
620    return m_Root.equals(((Trie) o).getRoot());
621  }
622
623  /**
624   * returns the common prefix for all the nodes
625   *
626   * @return            the result of the search
627   */
628  public String getCommonPrefix() {
629    return m_Root.getCommonPrefix();
630  }
631
632  /**
633   * returns the root node of the trie
634   *
635   * @return            the root node
636   */
637  public TrieNode getRoot() {
638    return m_Root;
639  }
640
641  /**
642   * returns all stored strings that match the given prefix
643   *
644   * @param prefix      the prefix that all strings must have
645   * @return            all strings that match the prefix
646   */
647  public Vector<String> getWithPrefix(String prefix) {
648    Vector<String>      result;
649    TrieNode            node;
650    TrieIterator        iter;
651   
652    result = new Vector<String>();
653   
654    if (containsPrefix(prefix)) {
655      node = m_Root.find(prefix);
656      iter = new TrieIterator(node);
657      while (iter.hasNext())
658        result.add(iter.next());
659    }
660   
661    return result;
662  }
663 
664  /**
665   * Returns the hash code value for this collection.
666   *
667   * @return            the hash code
668   */
669  public int hashCode() {
670    if (m_RecalcHashCode) {
671      m_HashCode       = toString().hashCode();
672      m_RecalcHashCode = false;
673    }
674   
675    return m_HashCode;
676  }
677 
678  /**
679   * Returns true if this collection contains no elements.
680   *
681   * @return            true if empty
682   */
683  public boolean isEmpty() {
684    return (m_Root.getChildCount() == 0);
685  }
686
687  /**
688   * Returns an iterator over the elements in this collection.
689   *
690   * @return            returns an iterator over all the stored strings
691   */
692  public Iterator<String> iterator() {
693    return new TrieIterator(m_Root);
694  }
695 
696  /**
697   * Removes a single instance of the specified element from this collection,
698   * if it is present.
699   *
700   * @param o           the object to remove
701   * @return            true if this collection changed as a result of the call
702   */
703  public boolean remove(Object o) {
704    boolean     result;
705   
706    result = m_Root.remove(((String) o) + TrieNode.STOP);
707   
708    m_RecalcHashCode = result;
709   
710    return result;
711  }
712 
713  /**
714   * Removes all this collection's elements that are also contained in the
715   * specified collection
716   *
717   * @param c           the collection to remove
718   * @return            true if the collection changed
719   */
720  public boolean removeAll(Collection<?> c) {
721    boolean     result;
722    Iterator    iter;
723   
724    result = false;
725   
726    iter = c.iterator();
727    while (iter.hasNext()) {
728      result = remove(iter.next()) || result;
729    }
730   
731    m_RecalcHashCode = result;
732   
733    return result;
734  }
735 
736  /**
737   * Retains only the elements in this collection that are contained in
738   * the specified collection
739   *
740   * @param c           the collection to use as reference
741   * @return            true if this collection changed as a result of the call
742   */
743  public boolean retainAll(Collection<?> c) {
744    boolean     result;
745    Iterator    iter;
746    Object      o;
747   
748    result = false;
749    iter   = iterator();
750    while (iter.hasNext()) {
751      o = iter.next();
752      if (!c.contains(o))
753        result = remove(o) || result;
754    }
755   
756    m_RecalcHashCode = result;
757
758    return result;
759  }
760 
761  /**
762   * Returns the number of elements in this collection.
763   *
764   * @return            the number of nodes in the tree
765   */
766  public int size() {
767    return m_Root.size();
768  }
769 
770  /**
771   * Returns an array containing all of the elements in this collection.
772   *
773   * @return            the stored strings as array
774   */
775  public Object[] toArray() {
776    return toArray(new String[0]);
777  }
778 
779  /**
780   * Returns an array containing all of the elements in this collection; the
781   * runtime type of the returned array is that of the specified array.
782   *
783   * @param a           the array into which the elements of this collection
784   *                    are to be stored
785   * @return            an array containing the elements of this collection
786   */
787  public <T> T[] toArray(T[] a) {
788    T[] result;
789    Iterator<T> iter;
790    Vector<T>   list;
791    int         i;
792   
793    list = new Vector<T>();
794    iter = Utils.<Iterator<T>>cast(iterator());
795    while (iter.hasNext())
796      list.add(iter.next());
797   
798    if (Array.getLength(a) != list.size())
799      result = Utils.<T[]>cast(Array.newInstance(a.getClass().getComponentType(), list.size()));
800    else
801      result = a;
802   
803    for (i = 0; i < list.size(); i++)
804      result[i] = list.get(i);
805   
806    return result;
807  }
808
809  /**
810   * returns the node as String
811   *
812   * @param node        the node to turn into a string
813   * @return            the node as string
814   */
815  protected String toString(TrieNode node) {
816    StringBuffer        result;
817    int                 i;
818    StringBuffer        indentation;
819   
820    result = new StringBuffer();
821   
822    // indent the node
823    indentation = new StringBuffer();
824    for (i = 0; i < node.getLevel(); i++)
825      indentation.append(" | ");
826    result.append(indentation.toString());
827   
828    // add the node label
829    if (node.getChar() == null)
830      result.append("<root>");
831    else if (node.getChar() == TrieNode.STOP)
832      result.append("STOP");
833    else
834      result.append("'" + node.getChar() + "'");
835    result.append("\n");
836
837    // add the children
838    for (i = 0; i < node.getChildCount(); i++)
839      result.append(toString((TrieNode) node.getChildAt(i)));
840   
841    return result.toString();
842  }
843 
844  /**
845   * returns the trie in string representation
846   *
847   * @return            the trie as string
848   */
849  public String toString() {
850    return toString(m_Root);
851  }
852 
853  /**
854   * Returns the revision string.
855   *
856   * @return            the revision
857   */
858  public String getRevision() {
859    return RevisionUtils.extract("$Revision: 5953 $");
860  }
861 
862  /**
863   * Only for testing (prints the built Trie). Arguments are added to the Trie.
864   * If not arguments provided then a few default strings are uses for building.
865   *
866   * @param args        commandline arguments
867   */
868  public static void main(String[] args) {
869    String[] data;
870   
871    if (args.length == 0) {
872      data = new String[3];
873      data[0] = "this is a test";
874      data[1] = "this is another test";
875      data[2] = "and something else";
876    }
877    else {
878      data = args.clone();
879    }
880
881    // build trie
882    Trie t = new Trie();
883    for (int i = 0; i < data.length; i++)
884      t.add(data[i]);
885    System.out.println(t);
886  }
887}
Note: See TracBrowser for help on using the repository browser.