You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2013/10/19 21:54:37 UTC

svn commit: r1533813 - in /lucene/dev/branches/lucene4956/lucene/analysis/arirang/src: java/org/apache/lucene/analysis/ko/dic/ java/org/apache/lucene/analysis/ko/morph/ java/org/apache/lucene/analysis/ko/utils/ resources/org/apache/lucene/analysis/ko/d...

Author: rmuir
Date: Sat Oct 19 19:54:37 2013
New Revision: 1533813

URL: http://svn.apache.org/r1533813
Log:
LUCENE-4956: refactor syllable handling to not be a list of thousands of arrays

Added:
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/resources/org/apache/lucene/analysis/ko/dic/syllable.dat   (with props)
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/test/org/apache/lucene/analysis/ko/utils/TestSyllableUtil.java   (with props)
Removed:
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/resources/org/apache/lucene/analysis/ko/dic/syllable.dic
Modified:
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/dic/DictionaryResources.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/MorphAnalyzer.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/WordSpaceAnalyzer.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/EomiUtil.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/MorphUtil.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/NounUtil.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/SyllableUtil.java
    lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/tools/java/org/apache/lucene/analysis/ko/dic/DictionaryBuilder.java

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/dic/DictionaryResources.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/dic/DictionaryResources.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/dic/DictionaryResources.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/dic/DictionaryResources.java Sat Oct 19 19:54:37 2013
@@ -31,8 +31,6 @@ import org.apache.lucene.util.IOUtils;
  */
 public class DictionaryResources {
   
-  public static final String FILE_SYLLABLE_FEATURE = "syllable.dic";
-  
   public static final String FILE_DICTIONARY = "dictionary.dic";  
   
   public static final String FILE_JOSA = "josa.dic";
@@ -49,6 +47,7 @@ public class DictionaryResources {
   
   public static final String FILE_UNCOMPOUNDS = "uncompounds.dic";
   
+  public static final String FILE_SYLLABLE_DAT = "syllable.dat";
   public static final String FILE_HANJA_IDX = "hanja.idx";
   public static final String FILE_HANJA_DAT = "hanja.dat";
   public static final int DATA_VERSION = 0;

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/MorphAnalyzer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/MorphAnalyzer.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/MorphAnalyzer.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/MorphAnalyzer.java Sat Oct 19 19:54:37 2013
@@ -226,24 +226,28 @@ public class MorphAnalyzer {
     boolean isVerbOnly = false;
     analysisWithEomi(input,"",candidates);
     
-    for(int i=strlen-1;i>0;i--) {
+    for (int i = strlen-1; i > 0; i--) {
       
-      String stem = input.substring(0,i);
+      String stem = input.substring(0, i);
       String eomi = input.substring(i);
 
-      char[] feature =  SyllableUtil.getFeature(eomi.charAt(0));    
-      if(!isVerbOnly&&josaFlag&&feature[SyllableUtil.IDX_JOSA1]=='1') {        
-        analysisWithJosa(stem,eomi,candidates);
+      char ch = eomi.charAt(0);    
+      if (!isVerbOnly && josaFlag && SyllableUtil.hasFeature(ch, SyllableUtil.JOSA1)) {        
+        analysisWithJosa(stem, eomi, candidates);
       }
       
-      if(eomiFlag) {      
-        analysisWithEomi(stem,eomi,candidates);
+      if (eomiFlag) {      
+        analysisWithEomi(stem, eomi, candidates);
+        eomiFlag &= SyllableUtil.hasFeature(ch, SyllableUtil.EOMI2);
       }      
       
-      if(josaFlag&&feature[SyllableUtil.IDX_JOSA2]=='0') josaFlag = false;
-      if(eomiFlag&&feature[SyllableUtil.IDX_EOMI2]=='0') eomiFlag = false;
+      if (josaFlag) {
+        josaFlag &= SyllableUtil.hasFeature(ch, SyllableUtil.JOSA2);
+      }
       
-      if(!josaFlag&&!eomiFlag) break;
+      if (!josaFlag && !eomiFlag) {
+        break;
+      }
     }
   }
   
@@ -359,8 +363,8 @@ public class MorphAnalyzer {
       output.setScore(AnalysisOutput.SCORE_CORRECT);
       MorphUtil.buildPtnVM(output, candidates);
       
-      char[] features = SyllableUtil.getFeature(stem.charAt(stem.length()-1)); // ㄹ불규칙일 경우
-      if((features[SyllableUtil.IDX_YNPLN]=='0'||morphs[1].charAt(0)!='ㄴ')
+      char ch = stem.charAt(stem.length()-1); // ㄹ불규칙일 경우
+      if ((SyllableUtil.hasFeature(ch, SyllableUtil.YNPLN) == false || morphs[1].charAt(0) != 'ㄴ')
           &&!"는".equals(end))   // "갈(V),는" 분석될 수 있도록
         return;
     }

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/WordSpaceAnalyzer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/WordSpaceAnalyzer.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/WordSpaceAnalyzer.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/morph/WordSpaceAnalyzer.java Sat Oct 19 19:54:37 2013
@@ -50,7 +50,7 @@ public class WordSpaceAnalyzer {
     
     for(int i=0;i<input.length();i++) {           
       
-      char[] f = SyllableUtil.getFeature(input.charAt(i));
+      char ch = input.charAt(i);
       
       String prefix = i==input.length()-1 ? "X" : input.substring(wStart,i+2);          
       Iterator<String[]> iter = DictionaryUtil.findWithPrefix(prefix);
@@ -73,12 +73,15 @@ public class WordSpaceAnalyzer {
         candidates.add(buildSingleOutput(entry));
         
       // 현 음절이 조사나 어미가 시작되는 음절일 가능성이 있다면... 
-      } else if(f[SyllableUtil.IDX_EOGAN]=='1'||f[SyllableUtil.IDX_JOSA1]=='1'){        
-        if(f[SyllableUtil.IDX_JOSA1]=='1') 
+      } else if (SyllableUtil.hasFeature(ch, SyllableUtil.EOGAN) || 
+                 SyllableUtil.hasFeature(ch, SyllableUtil.JOSA1)) {        
+        if (SyllableUtil.hasFeature(ch, SyllableUtil.JOSA1)) { 
           candidates.addAll(anlysisWithJosa(input.substring(wStart), i-wStart));
+        }
 
-        if(f[SyllableUtil.IDX_EOGAN]=='1') 
+        if (SyllableUtil.hasFeature(ch, SyllableUtil.EOGAN)) { 
           candidates.addAll(anlysisWithEomi(input.substring(wStart), i-wStart));
+        }
       }
   
       // 후보가 될 가능성이 높은 순으로 정렬한다.
@@ -156,25 +159,20 @@ public class WordSpaceAnalyzer {
     if(jend==-1) return candidates; // 타당한 조사가 아니라면...
   
     String input = snippet.substring(0,jend);
-
-    boolean josaFlag = true;
     
-    for(int i=input.length()-1;i>0;i--) {
-      
+    for (int i = input.length()-1; i > 0; i--) {
       String stem = input.substring(0,i);
-      
       String josa = input.substring(i);
 
-      char[] feature =  SyllableUtil.getFeature(josa.charAt(0));  
+      char ch = josa.charAt(0);  
       
-      if(josaFlag&&feature[SyllableUtil.IDX_JOSA1]=='1') {
-        morphAnal.analysisWithJosa(stem,josa,candidates);       
+      if (SyllableUtil.hasFeature(ch, SyllableUtil.JOSA1)) {
+        morphAnal.analysisWithJosa(stem, josa, candidates);       
       }
-        
-      if(josaFlag&&feature[SyllableUtil.IDX_JOSA2]=='0') josaFlag = false;
-      
-      if(!josaFlag) break;
       
+      if (!SyllableUtil.hasFeature(ch, SyllableUtil.JOSA2)) {
+        break;
+      }      
     }
     
     if(input.length()==1) {
@@ -209,9 +207,10 @@ public class WordSpaceAnalyzer {
     }
     
     // 조사의 2음절로 사용될 수 마지막 음절을 찾는다.
-    for(int i=jstart+1;i<snippet.length();i++) {
-      char[] f = SyllableUtil.getFeature(snippet.charAt(i));
-      if(f[SyllableUtil.IDX_JOSA2]=='0') break;
+    for (int i = jstart+1; i < snippet.length(); i++) {
+      if (!SyllableUtil.hasFeature(snippet.charAt(i), SyllableUtil.JOSA2)) {
+        break;
+      }
       jend = i;       
     }
         
@@ -364,36 +363,27 @@ public class WordSpaceAnalyzer {
     return candidates;
   }
   
-  private void anlysisWithEomiDetail(String input, List<AnalysisOutput> candidates ) 
-  {
-
-    boolean eomiFlag = true;
-    
+  private void anlysisWithEomiDetail(String input, List<AnalysisOutput> candidates) {
     int strlen = input.length();
     
     char ch = input.charAt(strlen-1);
-    char[] feature =  SyllableUtil.getFeature(ch);
     
-    if(feature[SyllableUtil.IDX_YNPNA]=='1'||feature[SyllableUtil.IDX_YNPLA]=='1'||
-        feature[SyllableUtil.IDX_YNPMA]=='1')
+    if (SyllableUtil.hasFeature(ch, SyllableUtil.YNPNA) ||
+        SyllableUtil.hasFeature(ch, SyllableUtil.YNPLA) ||
+        SyllableUtil.hasFeature(ch, SyllableUtil.YNPMA)) {
       morphAnal.analysisWithEomi(input,"",candidates);
+    }
     
-    for(int i=strlen-1;i>0;i--) {
-      
+    for (int i = strlen-1; i > 0; i--) {
       String stem = input.substring(0,i);
       String eomi = input.substring(i);
 
-      feature =  SyllableUtil.getFeature(eomi.charAt(0));   
-      
-      if(eomiFlag) {      
-        morphAnal.analysisWithEomi(stem,eomi,candidates);
-      }     
+      morphAnal.analysisWithEomi(stem,eomi,candidates); 
       
-      if(eomiFlag&&feature[SyllableUtil.IDX_EOMI2]=='0') eomiFlag = false;
-      
-      if(!eomiFlag) break;
+      if (!SyllableUtil.hasFeature(eomi.charAt(0), SyllableUtil.EOMI2)) {
+        break;
+      }
     }
-    
   }
   
   /**
@@ -420,9 +410,10 @@ public class WordSpaceAnalyzer {
 
     // 조사의 2음절로 사용될 수 마지막 음절을 찾는다.
     int start = 0;
-    for(int i=1;i<tail.length();i++) {
-      char[] f = SyllableUtil.getFeature(tail.charAt(i)); 
-      if(f[SyllableUtil.IDX_EOGAN]=='0') break;
+    for (int i = 1; i < tail.length(); i++) {
+      if (!SyllableUtil.hasFeature(tail.charAt(i), SyllableUtil.EOGAN)) {
+        break;
+      }
       start = i;        
     }
           
@@ -469,7 +460,7 @@ public class WordSpaceAnalyzer {
     
     int nEnd = output.getLastEnd()+o.getSource().length();
     
-    char[] f = nEnd<input.length() ? SyllableUtil.getFeature(input.charAt(nEnd)) : null;      
+    boolean hasJOSA1 = nEnd < input.length() ? SyllableUtil.hasFeature(input.charAt(nEnd), SyllableUtil.JOSA1) : false;      
     
     // 밥먹고 같은 경우가 가능하나.. 먹고는 명사가 아니다.
     if(po!=null&&po.getPatn()==PatternConstants.PTN_N&&candidates.size()>0&&  
@@ -485,7 +476,7 @@ public class WordSpaceAnalyzer {
     if(o.getPos()==PatternConstants.POS_NOUN && MorphUtil.hasVerbOnly(o.getStem())) {   
       output.removeLast();    
       return -1;
-    }else if(nEnd<input.length() && f[SyllableUtil.IDX_JOSA1]=='1' 
+    }else if(nEnd<input.length() && hasJOSA1 
       && DictionaryUtil.getNoun(o.getSource())!=null) {
       return -1;
     }else if(nEnd<input.length() && o.getScore()==AnalysisOutput.SCORE_ANALYSIS 
@@ -556,13 +547,13 @@ public class WordSpaceAnalyzer {
    * @return  if founded
    */
   private boolean findNounWithinStr(String str, int ws, int es) {
-
-    if(str.length()<es) return false;
+    if (str.length() < es) {
+      return false;
+    }
         
-    for(int i=es;i<str.length();i++) {
-      char[] f = SyllableUtil.getFeature(str.charAt(i));  
-      if(i==str.length() || (f[SyllableUtil.IDX_JOSA1]=='1')) {       
-        return (DictionaryUtil.getWord(str.substring(ws,i))!=null);
+    for (int i = es; i < str.length(); i++) {
+      if (SyllableUtil.hasFeature(str.charAt(i), SyllableUtil.JOSA1)) {       
+        return DictionaryUtil.getWord(str.substring(ws,i)) != null;
       }
     }
     

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/EomiUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/EomiUtil.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/EomiUtil.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/EomiUtil.java Sat Oct 19 19:54:37 2013
@@ -147,22 +147,18 @@ public class EomiUtil {
   }  
   
   public static boolean IsNLMBSyl(char ech, char lch) {
-  
-    char[] features = SyllableUtil.getFeature(ech);
-
     switch(lch) {
-
-      case 'ㄴ' :
-        return (features[SyllableUtil.IDX_YNPNA]=='1' || features[SyllableUtil.IDX_YNPLN]=='1');        
-      case 'ㄹ' :
-        return (features[SyllableUtil.IDX_YNPLA]=='1');
-      case 'ㅁ' :
-        return (features[SyllableUtil.IDX_YNPMA]=='1');    
-      case 'ㅂ' :
-        return (features[SyllableUtil.IDX_YNPBA]=='1');          
+      case 'ㄴ' : 
+        return SyllableUtil.hasFeature(ech, SyllableUtil.YNPNA) || SyllableUtil.hasFeature(ech, SyllableUtil.YNPLN);
+      case 'ㄹ' : 
+        return SyllableUtil.hasFeature(ech, SyllableUtil.YNPLA);
+      case 'ㅁ' : 
+        return SyllableUtil.hasFeature(ech, SyllableUtil.YNPMA);
+      case 'ㅂ' : 
+        return SyllableUtil.hasFeature(ech, SyllableUtil.YNPBA);
+      default: 
+        return false;
     }
-  
-    return false;
   }
   
   /**
@@ -213,7 +209,7 @@ public class EomiUtil {
     } 
     else if(chrs[0]!='ㅇ'&&
         (chrs[1]=='ㅏ'||chrs[1]=='ㅓ'||chrs[1]=='ㅔ'||chrs[1]=='ㅐ')&&
-        (chrs.length==2 || SyllableUtil.getFeature(estem)[SyllableUtil.IDX_YNPAH]=='1')&&
+        (chrs.length==2 || SyllableUtil.hasFeature(estem, SyllableUtil.YNPAH)) &&
         (DictionaryUtil.combineAndEomiCheck('어', end)!=null)) 
     {        
       strs[0] = stem;

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/MorphUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/MorphUtil.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/MorphUtil.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/MorphUtil.java Sat Oct 19 19:54:37 2013
@@ -136,10 +136,11 @@ public class MorphUtil {
   }
 
   public static boolean hasVerbOnly(String input) {
-    
-    for(int i=input.length()-1;i>=0;i--) {
-      char[] feature = SyllableUtil.getFeature(input.charAt(i));
-      if(feature[SyllableUtil.IDX_WDSURF]=='1'&&input.length()>i) return true;
+    for (int i = input.length()-1; i >=0; i--) {
+      if (SyllableUtil.hasFeature(input.charAt(i), SyllableUtil.WDSURF)) {
+        assert input.length() > i;
+        return true;
+      }
     }
     return false;
   }

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/NounUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/NounUtil.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/NounUtil.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/NounUtil.java Sat Oct 19 19:54:37 2013
@@ -242,20 +242,15 @@ public class NounUtil {
   }
       
   public static boolean endsWith2Josa(String input) {
-
-    boolean josaFlag = true;
-    for(int i=input.length()-2;i>0;i--) {
-        
+    for (int i = input.length()-2; i > 0; i--) {
       String josa = input.substring(i);
 
-      char[] feature =  SyllableUtil.getFeature(josa.charAt(0));    
-      if(josaFlag&&DictionaryUtil.existJosa(josa)) return true;
-  
-        
-      if(josaFlag&&feature[SyllableUtil.IDX_JOSA2]=='0') josaFlag = false;        
-      if(!josaFlag) break;
+      if (DictionaryUtil.existJosa(josa)) {
+        return true;
+      } else if (!SyllableUtil.hasFeature(josa.charAt(0), SyllableUtil.JOSA2)) {
+        return false;
+      }
     }
-      
     return false;
   }
       

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/SyllableUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/SyllableUtil.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/SyllableUtil.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/java/org/apache/lucene/analysis/ko/utils/SyllableUtil.java Sat Oct 19 19:54:37 2013
@@ -18,99 +18,69 @@ package org.apache.lucene.analysis.ko.ut
  */
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
+import java.io.InputStream;
 import org.apache.lucene.analysis.ko.dic.DictionaryResources;
-import org.apache.lucene.analysis.ko.dic.LineProcessor;
+import org.apache.lucene.codecs.CodecUtil;
+import org.apache.lucene.store.DataInput;
+import org.apache.lucene.store.InputStreamDataInput;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IOUtils;
 
 public class SyllableUtil {
   private SyllableUtil() {}
 
-  public static int IDX_JOSA1 = 0; // 조사의 첫음절로 사용되는 음절 49개
-  public static int IDX_JOSA2 = 1; // 조사의 두 번째 이상의 음절로 사용되는 음절 58개
-  public static int IDX_EOMI1 = 2; // 어미의 첫음절로 사용되는 음절 72개
-  public static int IDX_EOMI2 = 3; // 어미의 두 번째 이상의 음절로 사용되는 음절 105개
-  public static int IDX_YONG1 = 4; // 1음절 용언에 사용되는 음절 362개
-  public static int IDX_YONG2 = 5; // 2음절 용언의 마지막 음절로 사용되는 음절 316개
-  public static int IDX_YONG3 = 6; // 3음절 이상 용언의 마지막 음절로 사용되는 음절 195개
-  public static int IDX_CHEON1 = 7; // 1음절 체언에 사용되는 음절 680개
-  public static int IDX_CHEON2 = 8; // 2음절 체언의 마지막 음절로 사용되는 음절 916개
-  public static int IDX_CHEON3 = 9; // 3음절 체언의 마지막 음절로 사용되는 음절 800개
-  public static int IDX_CHEON4 = 10; // 4음절 체언의 마지막 음절로 사용되는 음절 610개
-  public static int IDX_CHEON5 = 11; // 5음절 이상 체언의 마지막 음절로 사용되는 음절 330개
-  public static int IDX_BUSA1 = 12; // 1음절 부사의 마지막 음절로 사용되는 음절 191개
-  public static int IDX_BUSA2 = 13; // 2음절 부사의 마지막 음절로 사용되는 음절 519개
-  public static int IDX_BUSA3 = 14; // 3음절 부사의 마지막 음절로 사용되는 음절 139개
-  public static int IDX_BUSA4 = 15; // 4음절 부사의 마지막 음절로 사용되는 음절 366개
-  public static int IDX_BUSA5 = 16; // 5음절 부사의 마지막 음절로 사용되는 음절 79개
-  public static int IDX_PRONOUN = 17; // 대명사의 마지막 음절로 사용되는 음절 77개
-  public static int IDX_EXCLAM = 18; // 관형사와 감탄사의 마지막 음절로 사용되는 음절 241개
-  
-  public static int IDX_YNPNA = 19; // (용언+'-ㄴ')에 의하여 생성되는 음절 129개
-  public static int IDX_YNPLA = 20; // (용언+'-ㄹ')에 의해 생성되는 음절 129개
-  public static int IDX_YNPMA = 21; // (용언+'-ㅁ')에 의해 생성되는 음절 129개
-  public static int IDX_YNPBA = 22; // (용언+'-ㅂ')에 의해 생성되는 음절 129개
-  public static int IDX_YNPAH = 23; // 모음으로 끝나는 음절 129개중 'ㅏ/ㅓ/ㅐ/ㅔ/ㅕ'로 끝나는 것이 선어말 어미 '-었-'과 결합할 때 생성되는 음절
-  public static int IDX_YNPOU = 24; // 모음 'ㅗ/ㅜ'로 끝나는 음절이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
-  public static int IDX_YNPEI = 25; // 모음 'ㅣ'로 끝나는 용언이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
-  public static int IDX_YNPOI = 26; // 모음 'ㅚ'로 끝나는 용언이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
-  public static int IDX_YNPLN = 27; // 받침 'ㄹ'로 끝나는 용언이 어미 '-ㄴ'과 결합할 때 생성되는 음절
-  public static int IDX_IRRLO = 28; // '러' 불규칙(8개)에 의하여 생성되는 음절 : 러, 렀
-  public static int IDX_IRRPLE = 29; // '르' 불규칙(193개)에 의하여 생성되는 음절 
-  public static int IDX_IRROO = 30; // '우' 불규칙에 의하여 생성되는 음절 : 퍼, 펐
-  public static int IDX_IRROU = 31; // '어' 불규칙에 의하여 생성되는 음절 : 해, 했
-  public static int IDX_IRRDA = 32; // 'ㄷ' 불규칙(37개)에 의하여 생성되는 음절
-  public static int IDX_IRRBA = 33; // 'ㅂ' 불규칙(446개)에 의하여 생성되는 음절
-  public static int IDX_IRRSA = 34; // 'ㅅ' 불규칙(39개)에 의하여 생성되는 음절
-  public static int IDX_IRRHA = 35; // 'ㅎ' 불규칙(96개)에 의하여 생성되는 음절 
-  public static int IDX_PEND = 36; // 선어말 어미 : 시 셨 았 었 였 겠
-  
-  public static int IDX_YNPEOMI = 37; // 용언이 어미와 결합할 때 생성되는 음절의 수 734개
-  
-  /**   용언의 표층 형태로만 사용되는 음절 */
-  public static int IDX_WDSURF = 38; 
+  /** 조사의 첫음절로 사용되는 음절 49개 */
+  public static int JOSA1 = 0;
+  /** 조사의 두 번째 이상의 음절로 사용되는 음절 58개 */
+  public static int JOSA2 = 1;
+  /** 어미의 두 번째 이상의 음절로 사용되는 음절 105개 */
+  public static int EOMI2 = 2;
+  /** (용언+'-ㄴ')에 의하여 생성되는 음절 129개 */
+  public static int YNPNA = 3;
+  /** (용언+'-ㄹ')에 의해 생성되는 음절 129개 */
+  public static int YNPLA = 4;
+  /** (용언+'-ㅁ')에 의해 생성되는 음절 129개 */
+  public static int YNPMA = 5;
+  /** (용언+'-ㅂ')에 의해 생성되는 음절 129개 */
+  public static int YNPBA = 6;
+  /** 모음으로 끝나는 음절 129개중 'ㅏ/ㅓ/ㅐ/ㅔ/ㅕ'로 끝나는 것이 선어말 어미 '-었-'과 결합할 때 생성되는 음절 */
+  public static int YNPAH = 7;
+  /** 받침 'ㄹ'로 끝나는 용언이 어미 '-ㄴ'과 결합할 때 생성되는 음절 */
+  public static int YNPLN = 8;
+  /** 용언의 표층 형태로만 사용되는 음절 */
+  public static int WDSURF = 9;
+  /** 어미 또는 어미의 변형으로 존재할 수 있는 음 (즉 IDX_EOMI 이거나 IDX_YNPNA 이후에 1이 있는 음절) */
+  public static int EOGAN = 10;
+  
+  private static final int NUM_FEATURES = 11;
+  private static final int HANGUL_START = 0xAC00;
+  private static final int HANGUL_END = 0xD7AF;
   
-  public static int IDX_EOGAN = 39; // 어미 또는 어미의 변형으로 존재할 수 있는 음 (즉 IDX_EOMI 이거나 IDX_YNPNA 이후에 1이 있는 음절)
-  
-  private static final List<char[]> syllables;  // 음절특성 정보
+  private static final FixedBitSet features;
   static {
-    try{
-      final List<char[]> list = new ArrayList<char[]>();
-      DictionaryResources.readLines(DictionaryResources.FILE_SYLLABLE_FEATURE, new LineProcessor() {
-        @Override
-        public void processLine(String line) throws IOException {
-          list.add(line.toCharArray());
-        }
-      });
-      syllables = Collections.unmodifiableList(list);
-    } catch(IOException ioe) {
+    InputStream stream = null;
+    try {
+      stream = DictionaryResources.class.getResourceAsStream(DictionaryResources.FILE_SYLLABLE_DAT);
+      DataInput dat = new InputStreamDataInput(stream);
+      CodecUtil.checkHeader(dat, DictionaryResources.FILE_SYLLABLE_DAT, DictionaryResources.DATA_VERSION, DictionaryResources.DATA_VERSION);
+      long bits[] = new long[dat.readVInt()];
+      for (int i = 0; i < bits.length; i++) {
+        bits[i] = dat.readLong();
+      }
+      features = new FixedBitSet(bits, (1 + HANGUL_END - HANGUL_START) * NUM_FEATURES);
+    } catch (IOException ioe) {
       throw new Error("Cannot load ressource", ioe);
-    } 
-  }
-  
-  /**
-   * 인덱스 값에 해당하는 음절의 특성을 반환한다.
-   * 영자 또는 숫자일 경우는 모두 해당이 안되므로 가장 마지막 글자인 '힣' 의 음절특성을 반환한다.
-   * 
-   * @param idx '가'(0xAC00)이 0부터 유니코드에 의해 한글음절을 순차적으로 나열한 값
-   */
-  public static char[] getFeature(int idx)  {
-    if(idx<0||idx>=syllables.size()) 
-      return syllables.get(syllables.size()-1);
-    else 
-      return syllables.get(idx); 
+    } finally {
+      IOUtils.closeWhileHandlingException(stream);
+    }
   }
   
-  /**
-   * 각 음절의 특성을 반환한다.
-   * @param syl  음절 하나
-   */
-  public static char[] getFeature(char syl) {
-    
-    int idx = syl - 0xAC00;
-    return getFeature(idx);
-    
+  /** Returns true if the syllable has the specified feature */
+  public static boolean hasFeature(char syl, int feature) {
+    if (syl < HANGUL_START || syl > HANGUL_END) {
+      return false; // outside of hangul syllable range
+    } else {
+      return features.get((syl - HANGUL_START) * NUM_FEATURES + feature);
+    }
   }
 }

Added: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/resources/org/apache/lucene/analysis/ko/dic/syllable.dat
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/resources/org/apache/lucene/analysis/ko/dic/syllable.dat?rev=1533813&view=auto
==============================================================================
Binary file - no diff available.

Added: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/test/org/apache/lucene/analysis/ko/utils/TestSyllableUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/test/org/apache/lucene/analysis/ko/utils/TestSyllableUtil.java?rev=1533813&view=auto
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/test/org/apache/lucene/analysis/ko/utils/TestSyllableUtil.java (added)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/test/org/apache/lucene/analysis/ko/utils/TestSyllableUtil.java Sat Oct 19 19:54:37 2013
@@ -0,0 +1,99 @@
+package org.apache.lucene.analysis.ko.utils;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.util.LuceneTestCase;
+
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.hasFeature;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.JOSA1;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.JOSA2;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.EOMI2;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPNA;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPLA;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPMA;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPBA;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPAH;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.YNPLN;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.WDSURF;
+import static org.apache.lucene.analysis.ko.utils.SyllableUtil.EOGAN;
+
+public class TestSyllableUtil extends LuceneTestCase {
+  
+  public void testGa() {
+    assertTrue(hasFeature('가', JOSA1));
+    assertTrue(hasFeature('가', JOSA2));
+    assertTrue(hasFeature('가', EOMI2));
+    assertFalse(hasFeature('가', YNPNA));
+    assertFalse(hasFeature('가', YNPLA));
+    assertFalse(hasFeature('가', YNPMA));
+    assertFalse(hasFeature('가', YNPBA));
+    assertFalse(hasFeature('가', YNPAH));
+    assertFalse(hasFeature('가', YNPLN));
+    assertFalse(hasFeature('가', WDSURF));
+    assertTrue(hasFeature('가', EOGAN));
+  }
+  
+  public void testGagg() {
+    assertNoFeatures('갂');
+  }
+  
+  public void testGan() {
+    assertFalse(hasFeature('간', JOSA1));
+    assertFalse(hasFeature('간', JOSA2));
+    assertTrue(hasFeature('간', EOMI2));
+    assertTrue(hasFeature('간', YNPNA));
+    assertFalse(hasFeature('간', YNPLA));
+    assertFalse(hasFeature('간', YNPMA));
+    assertFalse(hasFeature('간', YNPBA));
+    assertFalse(hasFeature('간', YNPAH));
+    assertTrue(hasFeature('간', YNPLN));
+    assertFalse(hasFeature('간', WDSURF));
+    assertTrue(hasFeature('간', EOGAN));
+  }
+  
+  public void testSomeFeatures() {
+    assertTrue(hasFeature('갔', WDSURF));
+    assertTrue(hasFeature('갔', YNPAH));
+    assertTrue(hasFeature('갑', YNPBA));
+    assertTrue(hasFeature('감', YNPMA));
+    assertTrue(hasFeature('갈', YNPLA));
+  }
+  
+  public void testOutOfBounds() {
+    for (int i = 0; i < 0xAC00; i++) {
+      assertNoFeatures((char)i);
+    }
+    for (int i = 0xD7B0; i <= 0xFFFF; i++) {
+      assertNoFeatures((char)i);
+    }
+  }
+  
+  private void assertNoFeatures(char ch) {
+    assertFalse(hasFeature(ch, JOSA1));
+    assertFalse(hasFeature(ch, JOSA2));
+    assertFalse(hasFeature(ch, EOMI2));
+    assertFalse(hasFeature(ch, YNPNA));
+    assertFalse(hasFeature(ch, YNPLA));
+    assertFalse(hasFeature(ch, YNPMA));
+    assertFalse(hasFeature(ch, YNPBA));
+    assertFalse(hasFeature(ch, YNPAH));
+    assertFalse(hasFeature(ch, YNPLN));
+    assertFalse(hasFeature(ch, WDSURF));
+    assertFalse(hasFeature(ch, EOGAN));
+  }
+}

Modified: lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/tools/java/org/apache/lucene/analysis/ko/dic/DictionaryBuilder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/tools/java/org/apache/lucene/analysis/ko/dic/DictionaryBuilder.java?rev=1533813&r1=1533812&r2=1533813&view=diff
==============================================================================
--- lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/tools/java/org/apache/lucene/analysis/ko/dic/DictionaryBuilder.java (original)
+++ lucene/dev/branches/lucene4956/lucene/analysis/arirang/src/tools/java/org/apache/lucene/analysis/ko/dic/DictionaryBuilder.java Sat Oct 19 19:54:37 2013
@@ -30,6 +30,7 @@ import org.apache.lucene.codecs.CodecUti
 import org.apache.lucene.store.DataOutput;
 import org.apache.lucene.store.OutputStreamDataOutput;
 import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.FixedBitSet;
 import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.packed.MonotonicBlockPackedWriter;
 import org.apache.lucene.util.packed.PackedInts;
@@ -44,7 +45,6 @@ public class DictionaryBuilder {
       DictionaryResources.FILE_JOSA,
       DictionaryResources.FILE_PREFIX,
       DictionaryResources.FILE_SUFFIX,
-      DictionaryResources.FILE_SYLLABLE_FEATURE,
       DictionaryResources.FILE_UNCOMPOUNDS
     };
     
@@ -56,6 +56,7 @@ public class DictionaryBuilder {
       copyAsIs(in, out);
     }
     buildHanjaMap(inputDir, outputDir);
+    buildSyllableDict(inputDir, outputDir);
   }
   
   static void copyAsIs(File in, File out) throws Exception {
@@ -120,4 +121,96 @@ public class DictionaryBuilder {
     idxStream.close();
     datStream.close();
   }
+
+  static void buildSyllableDict(File inputDir, File outputDir) throws Exception {
+    // Syllable features by index:
+    //  0: JOSA1: 조사의 첫음절로 사용되는 음절 49개
+    //  1: JOSA2: 조사의 두 번째 이상의 음절로 사용되는 음절 58개
+    //  2: EOMI1: 어미의 첫음절로 사용되는 음절 72개
+    //  3: EOMI2: 어미의 두 번째 이상의 음절로 사용되는 음절 105개
+    //  4: YONG1: 1음절 용언에 사용되는 음절 362개
+    //  5: YONG2: 2음절 용언의 마지막 음절로 사용되는 음절 316개
+    //  6: YONG3: 3음절 이상 용언의 마지막 음절로 사용되는 음절 195개
+    //  7: CHEON1: 1음절 체언에 사용되는 음절 680개
+    //  8: CHEON2: 2음절 체언의 마지막 음절로 사용되는 음절 916개
+    //  9: CHEON3: 3음절 체언의 마지막 음절로 사용되는 음절 800개
+    // 10: CHEON4: 4음절 체언의 마지막 음절로 사용되는 음절 610개
+    // 11: CHEON5: 5음절 이상 체언의 마지막 음절로 사용되는 음절 330개
+    // 12: BUSA1: 1음절 부사의 마지막 음절로 사용되는 음절 191개
+    // 13: BUSA2: 2음절 부사의 마지막 음절로 사용되는 음절 519개
+    // 14: BUSA3: 3음절 부사의 마지막 음절로 사용되는 음절 139개
+    // 15: BUSA4: 4음절 부사의 마지막 음절로 사용되는 음절 366개
+    // 16: BUSA5: 5음절 부사의 마지막 음절로 사용되는 음절 79개
+    // 17: PRONOUN: 대명사의 마지막 음절로 사용되는 음절 77개
+    // 18: EXCLAM: 관형사와 감탄사의 마지막 음절로 사용되는 음절 241개
+    // 19: YNPNA: (용언+'-ㄴ')에 의하여 생성되는 음절 129개
+    // 20: YNPLA: (용언+'-ㄹ')에 의해 생성되는 음절 129개
+    // 21: YNPMA: (용언+'-ㅁ')에 의해 생성되는 음절 129개
+    // 22: YNPBA: (용언+'-ㅂ')에 의해 생성되는 음절 129개
+    // 23: YNPAH: 모음으로 끝나는 음절 129개중 'ㅏ/ㅓ/ㅐ/ㅔ/ㅕ'로 끝나는 것이 선어말 어미 '-었-'과 결합할 때 생성되는 음절
+    // 24: YNPOU: 모음 'ㅗ/ㅜ'로 끝나는 음절이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
+    // 25: YNPEI: 모음 'ㅣ'로 끝나는 용언이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
+    // 26: YNPOI: 모음 'ㅚ'로 끝나는 용언이 '아/어'로 시작되는 어미나 선어말 어미 '-었-'과 결합할 때 생성되는 음절
+    // 27: YNPLN: 받침 'ㄹ'로 끝나는 용언이 어미 '-ㄴ'과 결합할 때 생성되는 음절
+    // 28: IRRLO: '러' 불규칙(8개)에 의하여 생성되는 음절 : 러, 렀
+    // 29: IRRPLE: '르' 불규칙(193개)에 의하여 생성되는 음절 
+    // 30: IRROO: '우' 불규칙에 의하여 생성되는 음절 : 퍼, 펐
+    // 31: IRROU: '어' 불규칙에 의하여 생성되는 음절 : 해, 했
+    // 32: IRRDA: 'ㄷ' 불규칙(37개)에 의하여 생성되는 음절
+    // 33: IRRBA: 'ㅂ' 불규칙(446개)에 의하여 생성되는 음절
+    // 34: IRRSA: 'ㅅ' 불규칙(39개)에 의하여 생성되는 음절
+    // 35: IRRHA: 'ㅎ' 불규칙(96개)에 의하여 생성되는 음절 
+    // 36: PEND: 선어말 어미 : 시 셨 았 었 였 겠
+    // 37: YNPEOMI: 용언이 어미와 결합할 때 생성되는 음절의 수 734개
+    // 38: WD_SURF: 용언의 표층 형태로만 사용되는 음절 
+    // 39: EOGAN: 어미 또는 어미의 변형으로 존재할 수 있는 음 (즉 IDX_EOMI 이거나 IDX_YNPNA 이후에 1이 있는 음절)
+    
+    OutputStream stream = new BufferedOutputStream(new FileOutputStream(new File(outputDir, DictionaryResources.FILE_SYLLABLE_DAT)));
+    DataOutput out = new OutputStreamDataOutput(stream);
+    CodecUtil.writeHeader(out, DictionaryResources.FILE_SYLLABLE_DAT, DictionaryResources.DATA_VERSION);
+    
+    int numBits = (1 + 0xD7AF - 0xAC00) * 11;
+    FixedBitSet features = new FixedBitSet(numBits);
+    int idx = 0;
+    
+    // (AC00-D7AF)
+    File input = new File(inputDir, "syllable.dic");
+    BufferedReader reader = new BufferedReader(IOUtils.getDecodingReader(input, IOUtils.CHARSET_UTF_8));
+    String line = null;
+    int last = 0xABFF;
+    while ((line = reader.readLine()) != null) {
+      if (!line.startsWith("!") && !line.startsWith("\uFEFF")) {
+        // validate (using the comments!)
+        final int ch;
+        String currentChar = line.substring(43);
+        if (currentChar.length() == 1) {
+          ch = currentChar.charAt(0);
+        } else {
+          ch = Integer.parseInt(currentChar, 16);
+        }
+        assert ch == last + 1;
+        last = ch;
+        // set feature bits
+        if (line.charAt(0) == '1') features.set(idx); idx++;
+        if (line.charAt(1) == '1') features.set(idx); idx++;
+        if (line.charAt(3) == '1') features.set(idx); idx++;
+        if (line.charAt(19) == '1') features.set(idx); idx++;
+        if (line.charAt(20) == '1') features.set(idx); idx++;
+        if (line.charAt(21) == '1') features.set(idx); idx++;
+        if (line.charAt(22) == '1') features.set(idx); idx++;
+        if (line.charAt(23) == '1') features.set(idx); idx++;
+        if (line.charAt(27) == '1') features.set(idx); idx++;
+        if (line.charAt(38) == '1') features.set(idx); idx++;
+        if (line.charAt(39) == '1') features.set(idx); idx++;
+      }
+    }
+    assert idx == numBits;
+    long raw[] = features.getBits();
+    out.writeVInt(raw.length);
+    for (int i = 0; i < raw.length; i++) {
+      out.writeLong(raw[i]);
+    }
+    reader.close();
+    stream.close();
+  }
 }