Mega Code Archive

 
Categories / Java / Collections Data Structure
 

Useful for string set lookups and command completion stuff

//package com.ryanm.util.text; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /**  * Useful for string set lookups and command completion stuff  *   * @author ryanm  */ public class RadixTree {   private Node root = new Node( "" );   private final boolean caseSensitive;   /**    * @param caseSensitive    *           <code>true</code> if case matters. Note that a    *           case-insensitive {@link RadixTree} will convert all    *           strings passed to it for insertion or query to lower    *           case.    */   public RadixTree( boolean caseSensitive )   {     this.caseSensitive = caseSensitive;     root.isString = false;   }   /**    * Adds string to the set    *     * @param string    */   public void add( CharSequence string )   {     if( !caseSensitive )     {       string = string.toString().toLowerCase();     }     root.addString( string );   }   /**    * Removes a string from the set    *     * @param string    */   public void remove( CharSequence string )   {     if( !caseSensitive )     {       string = string.toString().toLowerCase();     }     root.removeString( string );   }   /**    * Tests if the string is contained in the set    *     * @param string    * @return <code>true</code> if the entire string is contained in    *         the tree    */   public boolean contains( CharSequence string )   {     if( !caseSensitive )     {       string = string.toString().toLowerCase();     }     return findPredecessor( string ).length() == string.length();   }   /**    * Finds the substring of the string that is in the set    *     * @param string    * @return The substring that belongs    */   public String findPredecessor( CharSequence string )   {     if( !caseSensitive )     {       string = string.toString().toLowerCase();     }     StringBuilder buff = new StringBuilder();     root.findPredecessor( string, buff );     return buff.toString();   }   /**    * Finds possible completions that fit in the set    *     * @param string    * @param depth    *           How deeply to search the tree, the maximum number of    *           decisions that need to be made to type any one    *           completion    * @return A list of possible completions    */   public List<String> findSuccessors( CharSequence string, int depth )   {     if( !caseSensitive )     {       string = string.toString().toLowerCase();     }     List<String> completions = new LinkedList<String>();     root.findSuccessors( string, depth, completions );     return completions;   }   @Override   public String toString()   {     StringBuilder buff = new StringBuilder();     root.buildString( buff, -1 );     return buff.toString();   }   private class Node implements Comparable<Node>   {     private CharSequence value;     private Node[] children = new Node[ 0 ];     /**      * Indicates that the string ending at this node is a string in      * the set      */     private boolean isString = true;     private Node( CharSequence string )     {       value = string;     }     private void findSuccessors( CharSequence string, int depth, List<String> completions )     {       int d = findDivergenceIndex( string );       if( d < value.length() || d == string.length() )       {         StringBuilder prefix = new StringBuilder( value.subSequence( d, value.length() ) );         if( isString )         {           completions.add( prefix.toString() );         }         if( depth > 0 )         {           for( int i = 0; i < children.length; i++ )           {             children[ i ].getCompletions( prefix, depth - 1, completions );           }         }       }       else       {         Node c = findChild( string.charAt( d ) );         if( c != null )         {           c.findSuccessors( string.subSequence( d, string.length() ), depth, completions );         }       }     }     private void getCompletions( StringBuilder prefix, int depth, List<String> completions )     {       int pl = prefix.length();       prefix.append( value );       if( isString )       {         completions.add( prefix.toString() );       }       if( depth > 0 )       {         for( int i = 0; i < children.length; i++ )         {           children[ i ].getCompletions( prefix, depth - 1, completions );         }       }       prefix.delete( pl, prefix.length() );     }     private void addString( CharSequence string )     {       int d = findDivergenceIndex( string );       if( d < value.length() )       {         // need to split this node         Node child = new Node( value.subSequence( d, value.length() ) );         child.children = children;         child.isString = isString;         value = value.subSequence( 0, d );         children = new Node[] { child };         isString = false;       }       if( d == string.length() && d > 0 )       {         isString = true;       }       else       {         Node c = findChild( string.charAt( d ) );         if( c != null )         {           c.addString( string.subSequence( d, string.length() ) );         }         else         {           insertNode( new Node( string.subSequence( d, string.length() ) ) );         }       }     }     private void removeString( CharSequence string )     {       int d = findDivergenceIndex( string );       if( d == value.length() && d == string.length() )       {         isString = false;         if( children.length == 1 )         { // unify nodes           StringBuilder buff = new StringBuilder( value );           buff.append( children[ 0 ].value );           value = buff;           isString = children[ 0 ].isString;           children = children[ 0 ].children;         }       }       else       {         if( d == value.length() )         {           // check children           Node c = findChild( string.charAt( d ) );           if( c != null )           {             c.removeString( string.subSequence( d, string.length() ) );           }         }       }     }     private void findPredecessor( CharSequence string, StringBuilder buff )     {       int d = findDivergenceIndex( string );       if( d == value.length() && d <= string.length() )       { // this entire node was in the tree and there still some         // to go         buff.append( value.subSequence( 0, d ) );         // check children         if( d < string.length() )         {           CharSequence c = string.subSequence( d, string.length() );           Node child = findChild( c.charAt( 0 ) );           child.findPredecessor( c, buff );         }       }     }     private Node findChild( char c )     {       for( int i = 0; i < children.length; i++ )       {         if( c == children[ i ].value.charAt( 0 ) )         {           return children[ i ];         }       }       return null;     }     private int findDivergenceIndex( CharSequence string )     {       int d = 0;       while( d < value.length() && d < string.length() && value.charAt( d ) == string.charAt( d ) )       {         d++;       }       return d;     }     private void insertNode( Node child )     {       int i = Arrays.binarySearch( children, child );       assert i < 0;       i += 1;       i = -i;       Node[] nc = new Node[ children.length + 1 ];       System.arraycopy( children, 0, nc, 0, i );       nc[ i ] = child;       if( i < nc.length )       {         System.arraycopy( children, i, nc, i + 1, children.length - i );       }       children = nc;     }     @Override     public int compareTo( Node o )     {       return TextUtils.compareTo( value, o.value );     }     private void buildString( StringBuilder buff, int indent )     {       for( int i = 0; i < indent; i++ )       {         buff.append( " " );       }       if( isString )       {         buff.append( "\"" );       }       buff.append( value );       if( isString )       {         buff.append( "\"" );       }       indent++;       for( int i = 0; i < children.length; i++ )       {         buff.append( "\n" );         children[ i ].buildString( buff, indent );       }     }   } } /**  * Utility methods for dealing with text  *   * @author ryanm  */ class TextUtils {   /**    * Tests if s starts with t, ignoring the case of the characters    *     * @param s    * @param t    * @return <code>true</code> if s.toLowerCase().equals(    *         t.toLowerCase() ), but more efficiently    */   public static boolean startsWithIgnoreCase( CharSequence s, CharSequence t )   {     if( s.length() < t.length() )     {       return false;     }     for( int i = 0; i < t.length(); i++ )     {       char slc = Character.toLowerCase( s.charAt( i ) );       char tlc = Character.toLowerCase( t.charAt( i ) );       if( slc != tlc )       {         return false;       }     }     return true;   }   /**    * See {@link String#compareToIgnoreCase(String)}    *     * @param s    * @param t    * @return See {@link String#compareToIgnoreCase(String)}    */   public static int compareToIgnoreCase( CharSequence s, CharSequence t )   {     int i = 0;     while( i < s.length() && i < t.length() )     {       char a = Character.toLowerCase( s.charAt( i ) );       char b = Character.toLowerCase( t.charAt( i ) );       int diff = a - b;       if( diff != 0 )       {         return diff;       }       i++;     }     return s.length() - t.length();   }   /**    * See {@link String#compareTo(String)}    *     * @param s    * @param t    * @return See {@link String#compareTo(String)}    */   public static int compareTo( CharSequence s, CharSequence t )   {     int i = 0;     while( i < s.length() && i < t.length() )     {       char a = s.charAt( i );       char b = t.charAt( i );       int diff = a - b;       if( diff != 0 )       {         return diff;       }       i++;     }     return s.length() - t.length();   }   /**    * Splits a string    *     * @param composite    *           The composite string    * @param leftBracket    *           the opening parenthesis character    * @param rightBracket    *           the closing parenthesis character    * @param separator    *           The character that separates tokens. Separators that    *           lie between at least one pair of parenthesis are    *           ignored    * @return An array of individual tokens    */   public static String[] split( String composite, char leftBracket, char rightBracket,       char separator )   {     List<String> c = new ArrayList<String>();     int start = 0;     int i;     int lbcount = 0;     for( i = 0; i < composite.length(); i++ )     {       if( composite.charAt( i ) == leftBracket )       {         lbcount++;       }       else if( composite.charAt( i ) == rightBracket )       {         lbcount--;       }       else if( composite.charAt( i ) == separator && lbcount == 0 )       {         c.add( composite.substring( start, i ).trim() );         start = i + 1;       }     }     c.add( composite.substring( start, i ).trim() );     return c.toArray( new String[ c.size() ] );   }   /**    * Wraps the input string in {@code <html></html>} and breaks it up    * into lines with {@code <br>} elements. Useful for making    * multi-line tootips and the like.    *     * @param s    *           The input String    * @param lineLength    *           The desired length of the output lines.    * @return The HTMLised string    */   public static String HTMLiseString( String s, int lineLength )   {     if( s != null )     {       StringBuilder buff = new StringBuilder( s );       int lineStart = 0;       while( lineStart + lineLength < s.length() )       {         // find the first whitespace after the linelength         int firstSpaceIndex = buff.indexOf( " ", lineStart + lineLength );         // replace it with a <br>         if( firstSpaceIndex != -1 )         {           buff.deleteCharAt( firstSpaceIndex );           buff.insert( firstSpaceIndex, "<br>" );           lineStart = firstSpaceIndex + 4;         }         else         {           lineStart = s.length();         }       }       buff.insert( 0, "<html>" );       buff.append( "</html>" );       return buff.toString();     }     return null;   } }