原创:平衡的三叉树

去年3月份,写了一个平衡的三叉树算法包,还写了一个基于逆向最大匹配算法的中文分词算法包。现在,将平衡的三叉树算法包上传。首先看一下包结构:

1.chinese.utility.cfg代码:

package chinese.utility.cfg;

/**
 * 获得主词典、量词词典以及扩展词典和扩展停词词典的路径
 * @author TongXueQiang
 * @date 2015/11/25
 * @since 1.8
 */
public interface Configuration {
    public abstract String getMainDictionary();//主词典路径
}

package chinese.utility.cfg;

public class DefaultConfiguration implements Configuration {

    @Override
    public String getMainDictionary() {        
        return "chinese/utility/dictionary/word.dic";
    }    
}
2.chinese.utility.comparator代码:

package chinese.utility.comparator;

import java.util.Comparator;

import net.sourceforge.pinyin4j.PinyinHelper;
/**
 * 严格拼音比较器
 * @author TongXueQiang
 * @date 2016/03/05
 * @since JDK 1.8
 */
public class PinyinComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        for (int i = 0; i < o1.length() && i < o2.length(); i++) {

            int codePoint1 = o1.charAt(i);
            int codePoint2 = o2.charAt(i);

            if (Character.isSupplementaryCodePoint(codePoint1)
                    || Character.isSupplementaryCodePoint(codePoint2)) {
                i++;
            }

            if (codePoint1 != codePoint2) {
                if (Character.isSupplementaryCodePoint(codePoint1)
                        || Character.isSupplementaryCodePoint(codePoint2)) {
                    return codePoint1 - codePoint2;
                }

                String pinyin1 = pinyin((char) codePoint1);
                String pinyin2 = pinyin((char) codePoint2);

                if (pinyin1 != null && pinyin2 != null) { // 两个字符都是汉字
                    if (!pinyin1.equals(pinyin2)) {
                        return pinyin1.compareTo(pinyin2);
                    }
                } else {
                    return codePoint1 - codePoint2;
                }
            }
        }
        return o1.length() - o2.length();
    }

    /**
     * 字符的拼音,多音字就得到第一个拼音。不是汉字,就return null。
     */
    public String pinyin(char c) {
        String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c);
        if (pinyins == null) {
            return null;
        }
        return pinyins[0];
    }
}

3.chinese.utility.dictionary代码:

package chinese.utility.dictionary;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import chinese.utility.cfg.Configuration;
import chinese.utility.cfg.DefaultConfiguration;
import chinese.utility.ternaryTree.SortAndCompromise;
import chinese.utility.ternaryTree.TernaryNode;
import chinese.utility.ternaryTree.TernaryTree;
/**
 * 字典
 * @author TongXueQiang
 * @date 2016/03/06
 * @since JDK 1.8
 */
public class Dictionary {
    //主字典
    private static volatile Dictionary main_dict;
    //三叉树
    private TernaryTree tree;
    private Configuration configuration;
    
    private Dictionary () throws Exception{
        configuration = new DefaultConfiguration();
    }
    
    public static Dictionary getInstance() throws Exception{
        if (main_dict == null) {
            synchronized(Dictionary.class){
                if (main_dict == null) {
                    main_dict = new Dictionary();
                    return main_dict;
                }
            }
        }
        return main_dict;
    }
    
    /**
     * 从字典中读取汉字
     *
     * @param originalWords
     * @return
     */    
    public List<String> addToListFromDict(List<String> originalWords) throws Exception {
        //InputStream is = new FileInputStream("C:\\Users\\Administrator.2013-20150325HY\\Desktop\\word.dic");
        InputStream is = super.getClass().getClassLoader().getResourceAsStream(configuration.getMainDictionary());
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"), 512);
        String word = null;
        
        try {
            do {                
                word = br.readLine();                
                if (word == null) {
                    break;
                }
                
                if (word != null && !"".equals(word.trim())) {
                    originalWords.add(word);                    
                }                
            } while (word != null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                    is = null;
                }                
            } catch (Exception e) {
                e.printStackTrace();
            }            
        }
        return originalWords;
    }

    /**
     * 加载主字典
     * @throws Exception
     */
    public void loadDict() throws Exception {
        //把原来的字典替换为经过排序和折中处理的字典
        replaceDictionaryBySortAndCompromise();
        
        tree = new TernaryTree();
        InputStream is = super.getClass().getClassLoader().getResourceAsStream("chinese/utility/dictionary/word.dic");
        if (is == null) {
            throw new RuntimeException("Main Dictionary not found!!!");
        }
        
        try {            
            BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"),512);
            String newWord = null;
            do {
                newWord = br.readLine();                
                if (newWord != null && !"".equals(newWord.trim())) {
                    tree.insert(newWord);
                }
            }while(newWord != null);
        }catch(Exception e){
            e.printStackTrace();
        }
        finally{
            try {
                if (is != null) {
                    is.close();
                    is = null;
                }
            } catch(Exception e){
                e.printStackTrace();
            }
        }        
    }
    
    /**
     * 更新字典,对字典进行排序,然后折中处理
     * @throws Exception
     */
    private void replaceDictionaryBySortAndCompromise() throws Exception {
        SortAndCompromise sac = new SortAndCompromise();
        
        List<String> originalWords = new ArrayList<String>();
        OutputStream os = new FileOutputStream("E:/Eclipse_WorkSpace/balancedTernaryTree/src/chinese/utility/dictionary/word.dic");        
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os,"UTF-8"),512);
        //对原始字典进行排序,然后折中处理,输出到新的字典当中
        sac.sortAndCompromise(originalWords, writer);        
    }
    
    /**
     * 查找前缀匹配词语
     * @param prefix
     * @return
     */
    public List<TernaryNode> prefixCompletion(String prefix){
        return tree.prefixCompletion(prefix);
    }
}
word.dic为词典,保存格式为UTF-8无BOM编码格式。
4.chinese.utility.ternaryTree包代码:

package chinese.utility.ternaryTree;
/**
 * 三叉树节点
 * @author TongXueQiang
 * @date 2016/03/12
 * @since JDK 1.8
 */
public class TernaryNode {
    public char storedChar;    //节点存储的单个字符
    public String token;//最后一个节点存储的term(成词)
    public TernaryNode leftNode,centerNode,rightNode;//子节点
    
    public TernaryNode (char storedChar)  {
        this.storedChar = storedChar;
    }
}

 

package chinese.utility.ternaryTree;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 自定义三叉树
 * @author TongXueQiang
 * @date 2016/03/12
 * @since JDK 1.8
 */
public class TernaryTree {
    // 根节点,不存储字符
    private static TernaryNode root = new TernaryNode('\0');

    /**
     * 向树中插入字符
     *
     * @param TernaryNode
     * @param word
     * @return
     */
    public void insert(String word) {
        root = insert(root, word, 0);
    }

    public TernaryNode insert(TernaryNode currentTernaryNode, String word, int index) {
        if (word == null || word.length() < index) {
            return currentTernaryNode;
        }
        char[] charArray = word.toCharArray();
        
        if (currentTernaryNode == null) {
            currentTernaryNode = new TernaryNode(charArray[index]);
        }
        
        if (currentTernaryNode.storedChar > charArray[index]) {
            currentTernaryNode.leftNode = insert(currentTernaryNode.leftNode, word, index);
        } else if (currentTernaryNode.storedChar < charArray[index]) {
            currentTernaryNode.rightNode = insert(currentTernaryNode.rightNode, word, index);
        } else {
            if (index != word.length() - 1) {
                currentTernaryNode.centerNode = insert(currentTernaryNode.centerNode, word, index + 1);
            } else {
                currentTernaryNode.token = word;
            }
        }

        return currentTernaryNode;
    }

    /**
     * 查找以指定前缀开头的所有字符串
     *
     * @param prefix
     * @return
     */
    public List<TernaryNode> prefixCompletion(String prefix) {
        // 首先查找前缀的最后一个节点
        TernaryNode TernaryNode = findNode(prefix);
        return prefixCompletion(TernaryNode);
    }
    
    public List<TernaryNode> prefixCompletion(TernaryNode p) {
        List<TernaryNode> suggest = new ArrayList<TernaryNode>();
        
        if (p == null) return suggest;
        if ((p.centerNode == null) && (p.token == null)) return suggest;
        if ((p.centerNode == null) && (p.token != null)) {
          suggest.add(p);
          return suggest;
        }
        
        if (p.token != null) {
            suggest.add(p);
        }
        
        p = p.centerNode;

        Stack<TernaryNode> s = new Stack<TernaryNode>();
        s.push(p);
        while (!s.isEmpty()) {
            TernaryNode top = s.pop();            

            if (top.token != null) {
                suggest.add(top);
            }
            if (top.centerNode != null) {
                s.push(top.centerNode);
            }
            if (top.leftNode != null) {
                s.push(top.leftNode);
            }
            if (top.rightNode != null) {
                s.push(top.rightNode);
            }
        }
        return suggest;
    }

    /**
     * 查找前缀的下一个centerTernaryNode,作为searchPrefix的开始节点
     *
     * @param prefix
     * @return
     */
    public TernaryNode findNode(String prefix) {
        return findNode(root, prefix, 0);
    }
    
    private TernaryNode findNode(TernaryNode TernaryNode, String prefix, int index) {
        if (prefix == null || prefix.equals("")) {
            return null;
        }
        if (TernaryNode == null) {
            return null;
        }
        char[] charArray = prefix.toCharArray();
        // 如果当前字符小于当前节点存储的字符,查找左节点
        if (charArray[index] < TernaryNode.storedChar) {
            return findNode(TernaryNode.leftNode, prefix, index);
        }
        // 如果当前字符大于当前节点存储的字符,查找右节点
        else if (charArray[index] > TernaryNode.storedChar) {
            return findNode(TernaryNode.rightNode, prefix, index);
        } else {// 相等
            // 递归终止条件
            if (index !=charArray.length - 1) {
                return findNode(TernaryNode.centerNode, prefix, ++index);
            }
        }
        return TernaryNode;
    }
}

package chinese.utility.ternaryTree;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import chinese.utility.comparator.PinyinComparator;
import chinese.utility.dictionary.Dictionary;

public class SortAndCompromise {
    /**
     * 用二分法对经过排序的字典折中处理,输出到新的文件中
     *
     * @param word
     * @param left
     * @param right
     * @param newWord
     * @throws IOException
     */
    public void outputBalanced(BufferedWriter fp, List<String> k, int offset, int n) throws IOException {
        int m;
        if (n < 1) {
            return;
        }
        
        m = n >> 1; // m=n/2

        String item = k.get(m + offset);        
        fp.write(item);        
        fp.newLine();
        fp.flush();
        
        outputBalanced(fp, k, offset, m); // 输出左半部分
        outputBalanced(fp, k, offset + m + 1, n - m - 1); // 输出右半部分

    }

    /**
     * 排序和折中处理,以便外部调用
     *
     * @param originalWord
     * @param word
     * @return
     * @throws Exception
     */
    public void sortAndCompromise(List<String> originalWords, BufferedWriter writer) throws Exception {
        // 从字典中读取汉字,加载到list中
        Dictionary dict = Dictionary.getInstance();
        originalWords = dict.addToListFromDict(originalWords);

        // 对list中的汉字排序
        Comparator<String> comparator = new PinyinComparator();
        Collections.sort(originalWords, comparator);
        System.out.println("排序后的:" + originalWords);
        // 折中处理,输出到新的文件当中
        outputBalanced(writer, originalWords, 0, originalWords.size());
    }

}

5.chinese.utility.test代码:

package chinese.utility.test;

import java.io.IOException;
import java.util.List;

import org.junit.Test;
import chinese.utility.comparator.PinyinComparator;
import chinese.utility.ternaryTree.TernaryNode;
import chinese.utility.ternaryTree.TernaryTree;

public class HanyuToPinyinTest {    
    PinyinComparator comparator = new PinyinComparator();
    @Test
    public void test() throws IOException {
//         System.out.println(PinyinUtils.converterToSpell("重复").toLowerCase());
//       System.out.println(PinyinUtils.converterToSpell("康师傅").toLowerCase());
//         String []strs = PinyinUtils.pinyin('重');
//         Arrays.sort(strs,comparator);
//         for (String s : strs) {
//            System.out.println(s);
//         }    
        TernaryTree tree = new TernaryTree();
        tree.insert("cq");
        tree.insert("重庆");        
        
        List<TernaryNode> result = tree.prefixCompletion("cq");
        for (TernaryNode node : result) {
            System.out.println(node.token);
        }        
    }    
}

package chinese.utility.test;

import java.util.Comparator;

import org.junit.Assert;
import org.junit.Test;

import chinese.utility.comparator.PinyinComparator;

public class PinyinComparatorTest {
    private Comparator<String> comparator = new PinyinComparator();
     /**
     * 常用字
     */
     @Test
     public void testCommon() {
     Assert.assertTrue(comparator.compare("孟", "宋") < 0);
     }
    
     /**
     * 不同长度
     */
     @Test
     public void testDifferentLength() {
     Assert.assertTrue(comparator.compare("他奶奶的", "他奶奶的熊") < 0);
     }

     /**
     * 和非汉字比较
     */
     @Test
     public void testNoneChinese() {
     Assert.assertTrue(comparator.compare("a", "阿") < 0);
     Assert.assertTrue(comparator.compare("1", "阿") < 0);
     Assert.assertTrue(comparator.compare("b","阿") < 0);
     }

    /**
     * 非常用字(怡)
     */
    @Test
    public void testNoneCommon() {
        Assert.assertTrue(comparator.compare("怡", "张") < 0);
    }

    /**
     * 同音字
     */
    @Test
    public void testSameSound() {
        Assert.assertTrue(comparator.compare("怕", "帕") == 0);
    }
    
    /**
     * 多音字(曾)
     */
    @Test
    public void testMultiSound() {
        Assert.assertTrue(comparator.compare("曾经", "曾迪") > 0);
    }
}

package chinese.utility.test;

import java.util.List;
import chinese.utility.dictionary.Dictionary;
import chinese.utility.ternaryTree.TernaryNode;

public class Test {

    public static void main(String[] args) throws Exception {        
        Dictionary dictionary = Dictionary.getInstance();        
        //加载主字典
        dictionary.loadDict();
        //查找前缀匹配的词语
        List<TernaryNode> result = dictionary.prefixCompletion("中");
        for (TernaryNode node : result) {
            System.out.print(node.token+" ");
        }        
    }
}

 

6.chinese.utility.utils代码:

package chinese.utility.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
/**
 * 汉语转拼音
 * @author TongXueQiang
 * @date 2016/03/07
 * @since JDK 1.8
 */
public class PinyinUtils {
    
    /**   
     * 汉字转换为汉语拼音首字母,英文字符不变   
     * @param chines 汉字   
     * @return 拼音
     */     
    public static String converterToFirstSpell(String chines){              
         String pinyinName = "";   
          
         //转化为字符
         char[] nameChar = chines.toCharArray();
          
         //汉语拼音格式输出类   
         HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
          
         //输出设置,大小写,音标方式等   
         defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);       
         defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);       
          
         for (int i = 0; i < nameChar.length; i++) {       
             //如果是中文
             if (nameChar[i] > 128) {
                try {       
                     pinyinName +=
                           PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0].charAt(0);       
                 } catch (BadHanyuPinyinOutputFormatCombination e) {       
                     e.printStackTrace();       
                 }       
             }else{//为英文字符    
                 pinyinName += nameChar[i];       
             }       
         }       
        return pinyinName;       
     }       
         
    /**   
     * 汉字转换位汉语拼音,英文字符不变   
     * @param chines 汉字   
     * @return 拼音   
     */     
    public static String converterToSpell(String chines){               
        String pinyinName = "";       
        char[] nameChar = chines.toCharArray();
        
        //输出设置,大小写,音标方式等   
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();       
        defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);       
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        
        for (int i = 0; i < nameChar.length; i++) {       
            if (nameChar[i] > 128) {       
                try {       
                     pinyinName += PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0];       
                 } catch (BadHanyuPinyinOutputFormatCombination e) {       
                     e.printStackTrace();       
                 }       
             }else{       
                 pinyinName += nameChar[i];       
             }       
         }       
        return pinyinName;       
     }  
    /**
     * 得到一个字符的多音
     * @param c
     * @return
     */
    public static String [] pinyin(char c){
        return PinyinHelper.toHanyuPinyinStringArray(c) == null ? null : PinyinHelper.toHanyuPinyinStringArray(c);
    }
    
    public static List<String> getPermutationSentence(List<List<String>> termArrays,int start) {
          if (CollectionUtils.isEmpty(termArrays))
              return Collections.emptyList();

          int size = termArrays.size();
          
          if (start < 0 || start >= size) {
              return Collections.emptyList();
          }

          if (start == size-1) {
              return termArrays.get(start);
          }        

          List<String> strings = termArrays.get(start);

          List<String> permutationSentences = getPermutationSentence(termArrays, start + 1);

          if (CollectionUtils.isEmpty(strings)) {
              return permutationSentences;
          }

          if (CollectionUtils.isEmpty(permutationSentences)) {
              return strings;
          }

          List<String> result = new ArrayList<String>();
          for (String pre : strings) {
              for (String suffix : permutationSentences) {
                  result.add(pre+suffix);
              }
          }

          return result;
        }
}

posted @ 2017-02-04 21:22  佟学强  阅读(920)  评论(0编辑  收藏  举报