Package org.snu.ids.ha.ma

Source Code of org.snu.ids.ha.ma.MCandidate

/**
* <pre>
* </pre>
* @author  therocks
* @since  2007. 6. 4
*/
package org.snu.ids.ha.ma;


import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.snu.ids.ha.constants.Condition;
import org.snu.ids.ha.constants.POSTag;
import org.snu.ids.ha.dic.Dictionary;
import org.snu.ids.ha.dic.PDDictionary;
import org.snu.ids.ha.dic.SpacingPDDictionary;
import org.snu.ids.ha.util.Hangul;
import org.snu.ids.ha.util.Util;


/**
* <pre>
* 표현형에 대한 하나의 형태소 분석 후보를 저장한다.
* 분석 후보 형태소 목록에 덧붙여 접속 조건, 합성 조건 등의 부가 정보를 저장한다.
* </pre>
* @author   therocks
* @since  2007. 6. 4
*/
public class MCandidate
  extends MorphemeList
  implements Comparable<MCandidate>
{
  long  atlEnc  = 0// 접속 가능한 품사 정보          [A]ppendable[T]ag[L]ist[Enc]oded
  long  hclEnc  = 0// 현재 후보가 가진 접속 조건        [H]aving[C]ondition[L]ist[Enc]oded
  long  cclEnc  = 0// 접속할 때 확인해야 하는 조건        [C]hecking[C]ondition[L]ist[Enc]oded
  long  eclEnc  = 0// 이전에 나오지 말아야 하는 조건      [E]xclusion[C]ondition[L]ist[Enc]oded
  long  bclEnc  = 0// 현재 후보가 뒷 후보와 확인해야 하는 조건   [B]ackwardChecking[C]ondition[L]ist[Enc]oded
  /**
   * <pre>
   * 이전에 나오는 것이 선호되는 조건
   *   관형어 + 체언
   *   부사어 + 용언
   *   부사 + 관형사
   *   부사 + 부사
   * </pre>
   * @author  therocks
   */
  long  pclEnc  = 0// 이전에 나오는 것이 선호되는 조건      [P]refered[C]ondition[L]istEncoded
 
  /**
   * <pre>
   * 후보간의 Scoring을 위해서 완전한 사전어와, 후보사전어를 구분하여 사전어 길이 판단
   *  - 완전 사전어 : 완전히 한 단어로 인식된 사전어
   *    -> 예) 체언, 체언과 결합된 조사, 어간과 어미가 완전히 결합된 단어
   *  - 후보 사전어 : 사전에서 찾아졌지만, 그 완전성이 미비한 것
   *    -> 예) 체언과 않은 조사, 결합하지 않은 어미, 어간, 체언과 결합하지 않은 서술격 조사(활용 포함)
   * 점수 계산
   *  - MCandidate.calculateScore()
   * 최종적으로 Sorting할 때에는 MExpression.sortFinally()에 의해서 정렬 순서 재조정 됨
   * </pre>
   */
  byte  realDicLen    = 0;    // 실재 사전어로 취급할 수 있는 완전어의 길이
  byte  candDicLen    = 0;    // 후보 사전어의 길이
  byte  numOfSpace    = 0;    // 추가되어야 할 띄어쓰기의 수
  byte  numOfPrfrdCond  = 0;    // 선호 조건의 수
  byte  numOfXs      = 0;    // 접두어나 접미사의 수, 많이 포함할 수록 우선 순위를 낮추어준다.
  byte  numOfApndblMC  = 0;    // 접속 가능한 앞뒤 MCandidate의 갯수
  int    score      = 0;    // 계산된 점수
 
 
  /**
   * 계산된 확률값을 저장하기 위한 필드
   * @since  2009. 10. 15
   * @author  therocks
   */
  int    scoreOfBestMC  = 0;  
  float  lnprOfBestMC   = Float.NEGATIVE_INFINITY;
  byte   sizeOfBestMC   = 0;
  MCandidate prevBestMC  = null;
  /**
   * <pre>
   * 활용 규칙에 의해서 자동으로 생성되었는지 확인
   * </pre>
   * @since  2009. 09. 19
   * @author  therocks
   */
  boolean  autoExtd = false;
  /**
   * <pre>
   * 분리된 표현형을 저장하기 위함
   * 띄어쓰기 기준으로 각각의 표현형을 나타내는 문자열을 저장함
   * 띄어쓰기가 되어야 하는 시점에 공백 문자열을 추가하는 방법으로 띄어쓰기 구현
   * MCandidate.derive() 함수에서 띄어쓰기 처리
   * </pre>
   * @since  2007. 7. 26
   * @author  therocks
   */
  private ArrayList<String> expList = null; // 띄어쓰기 된 표층형 리스트
  /**
   * <pre>
   * 중복을 피하기 위해서 hashCode를 사용함.
   * MCandidate.getEncodedString().hashCode()을 사용하여 계산
   * hashCode를 중복하여 계산하는 것을 피하기 위해서 구성이 변하지 않으면 이미 계산된 hashCode를 사용하도록 함
   * <중요!!> 새로 계산되어야 하는 시점을 잘 파악하여 MCandidate.calculateHashCode() 함수를 호출해주어야 함!!
   * </pre>
   * @since  2007. 7. 26
   * @author  therocks
   */
  int hashCode = 0;    // 중복을 피하기 위한 hashCode를 미리 계산해두기 위함
 
  /**
   * <pre>
   * 띄어쓰기에 대한 확률값을 저장하기 위한 변수
   * </pre>
   * @since  2009. 12. 11
   * @author  Dongjoo
   */
  float lnprOfSpacing = 0;



  /**
   * <pre>
   * default constructor 기본 정보들을 설정해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   */
  private MCandidate()
  {
    super();
    expList = new ArrayList<String>();
  }


  /**
   * <pre>
   * 확인되지 않은 어휘에 대한 기분석 결과 생성
   * 미등록 명사에 대한 후보 생성시에만 사용됨~
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param string
   * @param index
   */
  MCandidate(String string, int index)
    throws Exception
  {
    this();
    add(new Morpheme(string, index));
    initConds(string);
    setExp(string);
    calcHashCode();
  }


  /**
   * <pre>
   * 활용하지 않는 어휘에 대한 기분석 결과 생성
   * Dictionary.loadFixed()에서만 사용됨
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param string
   * @param tag
   * @param compType
   */
  MCandidate(String string, String tag, String compType)
    throws Exception
  {
    this();
    add(new Morpheme(string, tag, compType));
    initConds(string);
    setExp(string);
    realDicLen = (byte)string.length();
  }


  /**
   * <pre>
   * 활용하는 동사, 형용사에 대한 어간 기분석 결과 생성
   * </pre>
   * @author  therocks
   * @since  2007. 7. 22
   * @param string
   * @param tag
   * @throws Exception
   */
  public MCandidate(String string, String tag)
    throws Exception
  {
    this();
    add(new Morpheme(string, tag, "S"));
    initConds(string);
    setExp(string);
  }


  /**
   * <pre>
   * 한글 이외의 Token정보를 받아들여서, Token을 형태소의 적합한 부분으로 설정해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param token
   */
  MCandidate(Token token)
    throws Exception
  {
    this();
    if( token.isCharSetOf(CharSetType.HANGUL) ) {
      throw new Exception("Token이 한글입니다.");
    }
    // 미등록어 분석 결과 추가
    add(new Morpheme(token));
    realDicLen = (byte) token.string.length();
    setExp(token.string);
    initConds();
  }


  /**
   * <pre>
   * 해당 표층어와 형태가 동일하고, 분석 결과의 길이가 1인 경우 첫번째 형태소의 품사를 반환
   * </pre>
   * @author  therocks
   * @since  2009. 08. 07
   * @param exp
   * @return the tag
   */
  public String getTag()
  {
    if( size() == 1 ) {
      return get(0).getTag();
    }
    return null;
  }
 
 
  /**
   * <pre>
   * 분석 후보가 하나의 형태소이고, 해당 형태소가 주어진 태그 집합중에 하나인지 확인하여 반환
   * </pre>
   * @author  therocks
   * @since  2009. 10. 15
   * @param tags
   * @return
   */
  public boolean isTagOf(long tags)
  {
    if( size() == 1 ) {
      return get(0).isTagOf(tags);
    }
    return false;
  }


  public String getATL()
  {
    return POSTag.getTagStr(atlEnc);
  }


  public long getATLEnc()
  {
    return atlEnc;
  }


  public String getHCL()
  {
    return Condition.getCondStr(hclEnc);
  }


  public long getHCLEnc()
  {
    return hclEnc;
  }


  public String getCCL()
  {
    return Condition.getCondStr(cclEnc);
  }


  public long getCCLEnc()
  {
    return cclEnc;
  }


  public String getECL()
  {
    return Condition.getCondStr(eclEnc);
  }


  public long getECLEnc()
  {
    return eclEnc;
  }


  public String getPCL()
  {
    return Condition.getCondStr(pclEnc);
  }


  public long getPCLEnc()
  {
    return pclEnc;
  }


  public boolean isAutoExtd()
  {
    return autoExtd;
  }


  public void setAutoExtd(boolean autoExtd)
  {
    this.autoExtd = autoExtd;
  }


  /**
   * <pre>
   * 자모 조건을 생성해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   */
  public void initConds(String string)
  {
    // atlEnc: 품사별 기본 접속 가능 품사 초기화 
    addApndblTag(getBasicApndblTags());

    initHavingCond(string);

    // pclEnc: 품사별 기본 선호 조건 초기화
    addPreferedCond(getBasicPreferedConds());
  }


  /**
   * <pre>
   * 품사 및 음운 조건을 초기화 한다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 19
   * @param string
   */
  public void initHavingCond(String string)
  {
    // hclEnc: 음운 정보 초기화
    addHavingCond(getBasicPhonemeConds(string));

    // hclEnc: 품사별 기본 수반 조건 초기화   
    addHavingCond(getBasicHavingConds());
  }


  /**
   * <pre>
   * 특수문자 영문자에 대한 음운 정보 및 선호 조건 정보 설정
   * </pre>
   * @author  therocks
   * @since  2007. 7. 26
   */
  private void initConds()
  {
    if( !lastMorp.isCharSetOf(CharSetType.HANGUL) ) {
      if( lastMorp.isCharSetOf(CharSetType.ENGLISH) ) {
        addHavingCond(Condition.ENG);
      }
      addHavingCond(Condition.SET_FOR_UN);
      addHavingCond(Condition.N);
      addPreferedCond(Condition.D);
    }
  }


  /**
   * <pre>
   * 각 품사별로 띄어쓰기 없이 이전에 올 수 있는 품사를 설정해줌. 
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @return the basic appendable tag with encoding
   */
  public long getBasicApndblTags()
  {
    long tags = 0;

    // 체언은 접두어를 가질 수 있음.
    if( firstMorp.isTagOf(POSTag.NNA) ) {
      tags |= POSTag.XPN;
      // 보통 명사는 복합어를 만들어 낼 수 있음.
      //if( firstMorp.isTag(POSTag.NNG) ) tags |= POSTag.NNG;
    }
    // 용언은 용언 접두어를 가질 수 있음
    else if( firstMorp.isTagOf(POSTag.VV | POSTag.VA) ) {
      tags |= POSTag.XPV;
    }
    // 접미사는 명사와 접속 가능
    else if( firstMorp.isTagOf(POSTag.XS) ) {
      tags |= POSTag.NNA;
    }
    // 단위 의존 명사는 수사와 붙여서 쓰는 것 용인
    else if( firstMorp.isTagOf(POSTag.NNM | POSTag.NR) ) {
      tags |= POSTag.NR;
    }

    return tags;
  }


  /**
   * <pre>
   * 기본 음운 정보 조건을 encoding하여 반환한다.
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @param string
   * @return
   */
  public long getBasicPhonemeConds(String string)
  {
    long cond = 0;
    char lastCh = string.charAt(string.length() - 1);
    Hangul lastHg = Hangul.split(lastCh);

    // 자음 조건 설정
    if( lastHg.hasJong() ) {
      cond |= Condition.JAEUM;
    } else {
      cond |= Condition.MOEUM;
    }

    if( Hangul.MO_POSITIVE_SET.contains(lastHg.jung) ) {
      cond |= Condition.YANGSEONG;
    } else {
      cond |= Condition.EUMSEONG;
    }
   

    // 동사, 형용사에 대한 추가 설정
    if( lastMorp.isTagOf(POSTag.VP) ) {
      // 동사, 형용사의 겹모음은 었을 붙여주기 위해 자음 조건 추가해줌
      if( Hangul.MO_DOUBLE_SET.contains(lastHg.jung) ) {
        cond |= Condition.JAEUM;
      }
      // '하다'동사에 대한 처리
      if( lastCh == '하' ) {
        cond |= Condition.HA;
      }
      // '가다'동사에 대한 처리
      else if( lastCh == '가' ) {
        cond |= Condition.GADA;
      }
      // '오다'동사에 대한 처리
      else if( lastCh == '오' ) {
        cond |= Condition.ODA;
      }
      // 'ㄹ'받침 용언 설정
      else if( lastHg.jong == 'ㄹ' ) {
        cond |= Condition.LIEUL;
      }
    }
    // 어미에 대한 추가 설정
    else if( lastMorp.isTagOf(POSTag.ET) ) {
      if( lastMorp.string.equals("ㄴ") ) {
        cond |= Condition.NIEUN;
      } else if( lastMorp.string.equals("ㄹ") ) {
        cond |= Condition.LIEUL;
      } else if( lastMorp.string.equals("ㅁ") ) {
        cond |= Condition.MIEUM;
      }
    }

    return cond;
  }
 
 
  /**
   * <pre>
   * 각 품사별로 가지는 기본적인 조건 반환
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @return
   */
  public long getBasicHavingConds()
  {
    long cond = 0;
    // 체언
    if( lastMorp.isTagOf(POSTag.N | POSTag.ETN) ) {
      // 조사와 접속시의 정보를 설정해주어야 함
      cond |= Condition.N;
    }
    // 관형어
    else if( lastMorp.isTagOf(POSTag.MD | POSTag.ETD) ) {
      cond |= Condition.D;
    }
    // 부사어, 부사격 조사
    else if( lastMorp.isTagOf(POSTag.MA | POSTag.JKM ) ) {
      cond |= Condition.A;
    }
    // 동사, 형용사
    // 연결 어미
    else if( lastMorp.isTagOf(POSTag.ECS | POSTag.ECD) ) {
      cond |= Condition.EC;
    }

    return cond;
  }


  /**
   * <pre>
   * 각 품사별로 가지는 기본적인 선호 조건 반환
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @return
   */
  public long getBasicPreferedConds()
  {
    long cond = 0;
    // 체언은 관형사를 선호함
    if( firstMorp.isTagOf(POSTag.N) ) {
      cond |= Condition.D;
    }
    // 관형어, 부사, 동사, 형용사
    else if( firstMorp.isTagOf(POSTag.VP | POSTag.MD | POSTag.MA) ) {
      cond |= Condition.A;
    }
    // 보조 용언은 보조적 연결어미를 선호
    if( firstMorp.isTagOf(POSTag.VX) ) {
      cond |= Condition.EC;
    }
   
    return cond;
  }


  /**
   * <pre>
   * 복사본을 만들어서 반환한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 5
   * @return
   */
  public MCandidate copy()
  {
    MCandidate clone = new MCandidate();
    clone.addAll(this);
    clone.expList.addAll(this.expList);
    clone.atlEnc = this.atlEnc;
    clone.hclEnc = this.hclEnc;
    clone.cclEnc = this.cclEnc;
    clone.bclEnc = this.bclEnc;
    clone.eclEnc = this.eclEnc;
    clone.pclEnc = this.pclEnc;
    clone.candDicLen = this.candDicLen;
    clone.realDicLen = this.realDicLen;
    clone.numOfSpace = this.numOfSpace;
    clone.numOfPrfrdCond = this.numOfPrfrdCond;
    clone.numOfApndblMC = this.numOfApndblMC;
    clone.lnprOfBestMC = this.lnprOfBestMC;
    clone.sizeOfBestMC = this.sizeOfBestMC;
    clone.prevBestMC = this.prevBestMC;
    clone.hashCode = this.hashCode;
    return clone;
  }


  /**
   * <pre>
   * 각 후보들의 분석 결과에 대한 index(offset)정보를 설정해준다.
   * 분석 결과가 실제 문장보다 길어질 수 있으므로 offset정보는 정확히 일치하지는 않을 수도 있다.
   * 예를 들어 준말의 경우 원형으로 복원되기 때문에 길어진다.
   * "하0/길1/ 2/바3/란4/다5/" -> 하0/+기1/+를2/ 3/바라4/+ㄴ다6/
   * </pre>
   * @author  therocks
   * @since  2008. 03. 31
   * @param index
   */
  public void setIndex(int index)
  {
    Morpheme mp = null;
    int offset = 0;
    for( int i = 0, size = size(); i < size; i++ ) {
      mp = get(i);
      mp.setIndex(index + offset);
      offset += mp.string.length();
    }
  }


  /**
   * <pre>
   * 형태소 정보를 모두 붙여준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param mpList
   */
  public void addAll(MorphemeList mpList)
  {
    for( int i = 0, stop = mpList.size(); i < stop; i++ ) {
      add(mpList.get(i).copy());
    }
  }


  /**
   * <pre>
   * 접속 가능한 품사정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param tag
   */
  public void addApndblTag(String tag)
  {
    addApndblTag(POSTag.getTagNum(tag));
  }


  /**
   * <pre>
   * 접속 가능한 품사정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param tagNum
   */
  public void addApndblTag(long tagNum)
  {
    atlEnc |= tagNum;
  }


  /**
   * <pre>
   * 접속 가능한 품사정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param tags
   */
  public void addApndblTags(String[] tags)
  {
    for( int i = 0, stop = tags.length; i < stop; i++ ) {
      addApndblTag(tags[i]);
    }
  }


  /**
   * <pre>
   * 기분석 결과가 가지는 접속 조건을 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param cond
   */
  public void addHavingCond(String cond)
  {
    addHavingCond(Condition.getCondNum(cond));
  }
 
 
  /**
   * <pre>
   * 기분석 결과가 가지는 접속 조건을 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param conds
   */
  public void addHavingConds(String[] conds)
  {
    for( int i = 0, stop = conds.length; i < stop; i++ ) {
      addHavingCond(conds[i]);
    }
  }


  /**
   * <pre>
   * 기분석 결과가 가지는 접속 조건을 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param condNum
   */
  public void addHavingCond(long condNum)
  {
    hclEnc |= condNum;
    if( lastMorp.isTag(POSTag.ETD) && Condition.checkAnd(this.hclEnc, Condition.NIEUN) )
      bclEnc |= Condition.NIEUN;
    else if( lastMorp.isTag(POSTag.ETD) && Condition.checkAnd(this.hclEnc, Condition.LIEUL) )
      bclEnc |= Condition.LIEUL;
    else if( lastMorp.isTagOf(POSTag.ETN) && Condition.checkAnd(this.hclEnc, Condition.MIEUM) )
      bclEnc |= Condition.MIEUM;
    else if( lastMorp.isTagOf(POSTag.V) ) {
      // 'ㄹ', 'ㅎ' 탈락에 대한 후위 조건 확인
      bclEnc |= (this.hclEnc & Condition.MINUS_JA_SET);
      // 'ㅂ' 추가에 의한 후위 조건 확인
      if( Condition.checkAnd(hclEnc, Condition.BIEUB) && !Hangul.endsWith(lastMorp.string, "ㅂ")) bclEnc |= Condition.BIEUB;
    }
  }


  /**
   * <pre>
   * 주어진 조건을 가지고 있는지 확인
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param condNum
   * @return
   */
  public boolean isHavingCond(long condNum)
  {
    return Condition.checkAnd(hclEnc, condNum);
  }


  /**
   * <pre>
   * 후보 기분석 결과가 가진 조건 정보를 삭제한다.
   * @since 2009. 10. 15
   * 동시에 뒷결합 조건도 삭제한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   */
  public void clearHavingCondition()
  {
    this.hclEnc = 0l;
    this.bclEnc = 0l;
  }


  /**
   * <pre>
   * 기분석 결과가 접속시 확인해야하는 조건 정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param cond
   */
  public void addChkCond(String cond)
  {
    cclEnc |= Condition.getCondNum(cond);
  }


  /**
   * <pre>
   * 기분석 결과가 접속시 확인해야하는 조건 정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param conds
   */
  void addChkConds(String[] conds)
  {
    for( int i = 0, stop = conds.length; i < stop; i++ ) {
      addChkCond(conds[i]);
    }
  }


  /**
   * <pre>
   * 띄어쓰기가 가능할 때 추가될 수 있는 품사 정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param tag
   */
  public void addPreferedCond(String cond)
  {
    pclEnc |= Condition.getCondNum(cond);
  }


  /**
   * <pre>
   * 띄어쓰기가 가능할 때 추가될 수 있는 품사 정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param condNum
   */
  public void addPreferedCond(long condNum)
  {
    pclEnc |= condNum;
  }


  /**
   * <pre>
   * 띄어쓰기가 가능할 때 추가될 수 있는 품사 정보를 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param conds
   */
  public void addPreferedConds(String[] conds)
  {
    for( int i = 0, stop = conds.length; i < stop; i++ ) {
      addPreferedCond(conds[i]);
    }
  }


  /**
   * <pre>
   * 접속 배제 조건을 추가한다.
   * </pre>
   * @author  therocks
   * @since  2009. 10. 15
   * @param cond
   */
  void addExclusionCond(String cond)
  {
    eclEnc |= Condition.getCondNum(cond);
  }


  /**
   * <pre>
   * 배열로 주어진 배제 조건을 추가한다.
   * </pre>
   * @author  therocks
   * @since  2009. 10. 15
   * @param conds
   */
  void addExclusionConds(String[] conds)
  {
    for( int i = 0, stop = conds.length; i < stop; i++ ) {
      addExclusionCond(conds[i]);
    }
  }
 
 
  /**
   * <pre>
   * 배제 조건에 해당하여 접속이 불가능한지 확인한다.
   * 하나라도 조건을 만족하면 배제해야하는 것으로 간주.
   * </pre>
   * @author  therocks
   * @since  2009. 10. 14
   * @param exlCondEnc
   * @return
   */
  private boolean isCondExclusive(long exlCondEnc)
  {
    if( exlCondEnc == 0 ) return false;
    return Condition.checkOr(hclEnc, exlCondEnc);
  }


  /**
   * <pre>
   * 표현형을 설정해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 20
   * @param exp
   */
  public void setExp(String exp)
  {
    expList.clear();
    expList.add(exp);
  }


  /**
   * <pre>
   * 띄어쓰기 된 표층어를 반환
   * </pre>
   * @author  therocks
   * @since  2007. 6. 20
   * @return
   */
  public String getExp()
  {
    StringBuffer sb = new StringBuffer();
    for( int i = 0, stop = expList.size(); i < stop; i++ ) {
      sb.append(expList.get(i));
    }
    return sb.toString();
  }


  /**
   * <pre>
   * toIdx에 해당하는 순서까지의 표현형을 가져온다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 25
   * @param toIdx
   * @return
   */
  String getExp(int toIdx)
  {
    StringBuffer sb = new StringBuffer();
    for( int i = 0, stop = Math.min(expList.size(), toIdx + 1); i < stop; i++ ) {
      sb.append(expList.get(i));
    }
    return sb.toString();
  }


  /**
   * <pre>
   * 띄어쓰기를 /로 연결해서 반환한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 24
   * @return
   */
  String geExpStrWithSpace()
  {
    StringBuffer sb = new StringBuffer();
    for( int i = 0, stop = expList.size(); i < stop; i++ ) {
      if( i > 0 ) sb.append(" ");
      sb.append(expList.get(i));
    }
    return sb.toString();
  }


  /**
   * <pre>
   * 접속 가능한 조건인지 확인
   * 1) lastMorp.isTagOf(mcToAppend.atlEnc)
   *    뒷 분석 후보의 접속 가능 품사로 끝나는지 확인
   * 2) isCondApndbl(mcToAppend.cclEnc)
   *    뒷 분석 후보가 확인해야하는 조건이 있을 때, 이를 만족하는지 확인
   * 3) !isCondExclusive(mcToAppend.eclEnc)
   *    뒷 분석 후보가 배제 조건을 가질 때, 이를 하나라도 만족하는지 확인
   * </pre>
   * @author  therocks
   * @since  2007. 7. 27
   * @param mcToAppend
   * @return
   */
  public boolean isApndbl(MCandidate mcToAppend)
  {
    boolean ret = !isHavingCond(Condition.F);
    if( ret ) ret = lastMorp.isTagOf(mcToAppend.atlEnc);
    if( ret ) ret = Condition.checkAnd(hclEnc, mcToAppend.cclEnc);
    if( ret ) ret = !isCondExclusive(mcToAppend.eclEnc);
    // 어간의 변형을 동반한 활용에 의한 어미와의 결합을 확인
    if( ret && mcToAppend.firstMorp.isTagOf(POSTag.E) ) {
      ret = Condition.checkAnd(mcToAppend.cclEnc, bclEnc);
    }
    return ret;
  }


  /**
   * <pre>
   * 띄어쓰기가 되었을 때 연결이 가능한지 확인
   * 마지막이 활용의 시작이면, 다음은 반드시 선어말 혹은 어말 어미가 와야 함!!
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param mcToAppend
   * @return
   */
  boolean isApndblWithSpace(MCandidate mcToAppend)
  {
    // 띄어쓰기 불가능 확인
    if(
        // 어간, 선어말 어미, 접두사의 경우 뒷부분에 띄어쓰기를 추가하지 못함.
        lastMorp.isTagOf(POSTag.V | POSTag.EP | POSTag.XP)
        // 어미, 조사, 접미사, 
        || mcToAppend.firstMorp.isTagOf(POSTag.E | POSTag.XS | POSTag.VCP | POSTag.J)
        // 생략되어서 앞말과 이어져야 하는 경우
        || mcToAppend.isHavingCond(Condition.SHORTEN) )
    {
      return false;
    }

    return true;
  }


  /**
   * <pre>
   * 현재의 후보에 부착할 후보를 생성하여 반환해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param mcToAppend
   * @return
   */
  public MCandidate derive(MCandidate mcToAppend)
  {
    // 접속 가능 확인
    boolean isApndbl = this.isApndbl(mcToAppend);
   
    // 영문 + 조사로 결합될 때에는 붙여 쓰여있던 경우에만 결합하도록 함.
    if( isApndbl && this.lastMorp.isCharSetOf(CharSetType.ENGLISH) ) {
      if( this.lastMorp.index + this.lastMorp.string.length() != mcToAppend.firstMorp.index ) return null;
    }
   
    // 띄어쓰기 하여 접속 가능한지 확인
    boolean isApndblWithSpace = this.isApndblWithSpace(mcToAppend);

    // 접속 가능하지 않고, 띄어쓰기 오류도 아니면 후보 생성 안함
    if( !isApndbl && !isApndblWithSpace ) return null;
   
    // 이미 있는 용언 생성을 막음.
    if( isApndbl && mcToAppend.firstMorp.isTagOf(POSTag.VP)
        && Dictionary.getInstance().containVerbStem(this.lastMorp.string + mcToAppend.firstMorp.string) )
    {
      return null;
    }

    MCandidate mcNew = new MCandidate();

    // 분석 후보 생성
    mcNew = new MCandidate();
    mcNew.addAll(this);
    mcNew.addAll(mcToAppend);
    mcNew.expList.addAll(this.expList);
    mcNew.numOfSpace = (byte) (this.numOfSpace + mcToAppend.numOfSpace);
    mcNew.numOfPrfrdCond = (byte) (this.numOfPrfrdCond + mcToAppend.numOfPrfrdCond);
    mcNew.atlEnc = this.atlEnc;
    mcNew.hclEnc = mcToAppend.hclEnc;
    mcNew.bclEnc = mcToAppend.bclEnc;
    mcNew.cclEnc = this.cclEnc;
    mcNew.eclEnc = this.eclEnc;
    mcNew.pclEnc = this.pclEnc;

    // 접속 불가능이면, 띄어쓰기 추가
    if( !isApndbl ) {
      // 띄어쓰기 숫자 증가
      mcNew.numOfSpace++;

      // 접속 불가능일 때에만 선호 조건 추가
      mcNew.numOfPrfrdCond += (byte) Long.bitCount(hclEnc & mcToAppend.pclEnc);

      // 띄어쓰기, 어절 표현형 구분
      mcNew.add(this.size(), new MorphemeSpace(mcToAppend.atlEnc, this.hclEnc, this.bclEnc, mcToAppend.cclEnc, mcToAppend.eclEnc, mcToAppend.pclEnc));
      mcNew.expList.add("");
     
    }
   
   
    // 띄어쓰기가 처리된 표층어 정리
    // 띄어쓰기가 되었다면 "" 이 추가되었기 때문에 띄어쓰기 처리된 표현형으로 추가된다.
    mcNew.expList.add(
        // 이전 표층어에
        mcNew.expList.remove(mcNew.expList.size() - 1)
        // 바로 다음 표층어를 붙여서
        + mcToAppend.expList.get(0));
   
    // 나머지 표층어 추가
    mcNew.expList.addAll(mcToAppend.expList.subList(1, mcToAppend.expList.size()));

    if( isApndbl ) {
      // 결합된 위치의 품사가 결합 가능한 품사만 남도록 설정
      mcNew.get(size() - 1).infoEnc &= (0x8000000000000000l | mcToAppend.atlEnc);

      // 어말어미 + 어말어미인 경우에는 두 어말 어미를 합쳐준다.
      if( this.lastMorp.isTagOf(POSTag.EM) && mcToAppend.firstMorp.isTagOf(POSTag.EM) ) {
        mcNew.mergeAt(size() - 1);
      }
      // 부사화 접미사가 합쳐지면 부사로 만들어줌
      else if( mcToAppend.firstMorp.isTagOf(POSTag.XSM) ) {
        mcNew.mergeAt(size() - 1);
      }
      // 사랑+해 와 같이 [명사]+하다 형으로 만들어지는 경우에는 [명사]+하다 를 하나의 동사로 취급하여
      // 앞에 관형어가 오지 않고, 부사어가 올 수 있도록 조건을 수정해줌
      else if( mcToAppend.firstMorp.isTagOf(POSTag.VP) ) {
        mcNew.pclEnc = mcToAppend.pclEnc;
      }
    }

    // 띄어쓰기에 의한 확률 처리
    String temp1 = this.expList.get(this.expList.size() - 1);
    String temp2 = mcToAppend.expList.get(0);
    float lnpro = SpacingPDDictionary.getProb(temp1.charAt(temp1.length() - 1), temp2.charAt(0), !isApndbl);
   
    mcNew.setLnprOfSpacing(this.lnprOfSpacing + mcToAppend.lnprOfSpacing + lnpro);
   
    // 새로 만들어진 후보의 사전 어휘 길이 계산
    mcNew.calcDicLen();

    return mcNew;
  }


  /**
   * <pre>
   * 후보 분석 결과를 띄어쓰기를 기준으로 분리해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 20
   * @return
   */
  List<MCandidate> split()
  {
    // 첫번째가 공백으로 시작하면 삭제해준다.
    if( this.get(0) instanceof MorphemeSpace ) {
      expList.remove(0);
      remove(0);
    }

    ArrayList<MCandidate> ret = new ArrayList<MCandidate>();
    MCandidate mc = new MCandidate();
    mc.atlEnc = atlEnc;
    mc.cclEnc = cclEnc;
    mc.eclEnc = eclEnc;
    mc.pclEnc = pclEnc;
    Morpheme mp = null;
    int expIdx = 0;
    for( int i = 0, stop = size(); i < stop; i++ ) {
      mp = get(i);
      if( mp instanceof MorphemeSpace ) {
        if( i == 0 ) continue;
        mc.setExp(expList.get(expIdx));
        MorphemeSpace mps = ((MorphemeSpace)mp);
        mc.hclEnc = mps.hclEnc;
        mc.bclEnc = mps.bclEnc;
        mc.calcDicLen();
        mc.setLnprOfSpacing(SpacingPDDictionary.getProb(mc.getExp()));
        expIdx++;
        ret.add(mc);
       
        mc = new MCandidate();
        mc.atlEnc = mps.atlEnc;
        mc.cclEnc = mps.cclEnc;
        mc.eclEnc = mps.eclEnc;
        mc.pclEnc = mps.pclEnc;
      } else {
        mc.add(mp);
      }
    }
    mc.setExp(expList.get(expIdx));
    mc.hclEnc = hclEnc;
    mc.bclEnc = bclEnc;
    mc.calcDicLen();
    mc.setLnprOfSpacing(SpacingPDDictionary.getProb(mc.getExp()));
    ret.add(mc);
    return ret;
  }


  /**
   * <pre>
   * head tail 문자를 받아들여서 해당 head, tail로 잘라질 수 있는 위치를 찾아서,
   * head, tail로 잘라서 반환해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 25
   * @param headStr
   * @param headIdx
   * @param tailStr
   * @param tailIdx
   * @return
   * @throws Exception
   */
  MCandidate[] divideHeadTailAt(String headStr, int headIdx, String tailStr, int tailIdx)
    throws Exception
  {
    int divideIdx = 0;
    for( int i = 0, stop = expList.size(); i < stop; i++ ) {
      if( getExp(i).equals(headStr) ) {
        break;
      }
      divideIdx++;
    }
    if( numOfSpace <= divideIdx ) {
      return new MCandidate[] { new MCandidate(headStr, headIdx), new MCandidate(tailStr, tailIdx) };
    }
    MCandidate[] ret = new MCandidate[2];

    MCandidate headMC = ret[0] = new MCandidate();
    MCandidate tailMC = ret[1] = new MCandidate();

    // head 생성
    headMC.atlEnc = atlEnc;
    headMC.cclEnc = cclEnc;
    headMC.eclEnc = eclEnc;
    headMC.pclEnc = pclEnc;

    int spaceIdx = 0;
    int idx = 0, stop = size(), accIdx = 0;
    for( ; idx < stop; idx++ ) {
      Morpheme mp = get(idx);
      if( mp instanceof MorphemeSpace ) {
        if( spaceIdx < divideIdx ) {
          headMC.add(mp);
          spaceIdx++;
          continue;
        }
        for( int j = 0, jStop = divideIdx + 1; j < jStop; j++ ) {
          headMC.expList.add(expList.get(j));
        }
       
        MorphemeSpace mps = ((MorphemeSpace) mp);
        headMC.hclEnc = mps.hclEnc;
        headMC.bclEnc = mps.bclEnc;

        // tail 생성
        tailMC.atlEnc = mps.atlEnc;
        tailMC.hclEnc = hclEnc;
        tailMC.bclEnc = bclEnc;
        tailMC.cclEnc = mps.bclEnc;
        tailMC.eclEnc = mps.eclEnc;
        tailMC.pclEnc = mps.pclEnc;
        idx++;
        break;
      }
      mp.setIndex(headIdx + accIdx);
      accIdx += mp.getString().length();
      headMC.add(mp);
    }

    // 나머지 분석 결과 삽입
    if( idx < stop ) {
      for( ; idx < stop; idx++ ) {
        tailMC.add(get(idx));
      }
      // 표현형 추가
      for( int i = divideIdx + 1, iStop = expList.size(); i < iStop; i++ ) {
        tailMC.expList.add(expList.get(i));
      }
    }
    headMC.calcDicLen();
    tailMC.calcDicLen();
    headMC.setLnprOfSpacing(SpacingPDDictionary.getProb(headMC.getExp()));
    tailMC.setLnprOfSpacing(SpacingPDDictionary.getProb(tailMC.getExp()));

    return ret;
  }



  /**
   * <pre>
   * idx번째 띄어쓰기 다음에 오는 형태소가 미등록어인지 확인한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 25
   * @param idx
   * @return
   */
  boolean isUNBfrOrAftrIthSpace(int idx)
  {
    if( idx >= this.numOfSpace ) return false;
    int spaceIdx = 0;
    for( int i = 0, stop = size() - 1; i < stop; i++ ) {
      Morpheme mp = get(i);
      if( mp instanceof MorphemeSpace ) {
        if( spaceIdx != idx ) {
          spaceIdx++;
          continue;
        }
        mp = get(i + 1);
        return get(i + 1).isTag(POSTag.UN)
          || get(i - 1).isTag(POSTag.UN);
      }
    }
    return false;
  }


  /**
   * <pre>
   * 현재 분석 후보의 hashCode를 반환한다.
   * hashCode는 calculateDicLen() 호출시에 생성되는 문자열을 바탕으로 한번만 계산한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @return Hash code of the object
   */
  public int hashCode()
  {
    return hashCode;
  }


  /**
   * <pre>
   * hashCode를 계산한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 25
   */
  public void calcHashCode()
  {
    hashCode = getEncStr().hashCode();
  }


  /**
   * <pre>
   * 기분석 결과가 동일한지 확인
   * </pre>
   * @author  therocks
   * @since  2007. 6. 6
   * @param obj
   * @return
   */
  public boolean equals(Object obj)
  {
    return hashCode() == obj.hashCode();
  }


  /**
   * <pre>
   * 사전에 나온 어휘의 길이가 길수록 더 적합한 것이라고 판단하여
   * Sorting할 때 사용함!!
   * 분석 어휘의 수가 적은 것일 수록 유리하도록 설정!!
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param arg0
   * @return
   */
  public int compareTo(MCandidate comp)
  {
    return comp.getScore() - this.getScore();
  }


  /**
   * <pre>
   * 현재 분석 후보의 점수를 반환한다.
   *
   * # 점수 부여 정책
   *   완전 사전어의 길이
   *   후보 사전어의 길이
   *   띄어쓰기 수
   *   선호 조건의 갯수
   *   의 순으로 정렬될 수 있도록 점수 부여
   *
   * 점수 계산은 다음의 경우에만 수행된다.
   *   1) 기분석 사전 구축시
   *   2) 후보간의 derive 호출시
   *   3) bonus가 변할 때
   * </pre>
   * @author  therocks
   * @since  2007. 7. 22
   * @return
   */
  public int getScore()
  {
    score = this.realDicLen + this.candDicLen;
    score = score * 10 - numOfSpace;
    score = score * 100 + (int)lnprOfSpacing;
    score = score * 100 + this.realDicLen;
    score = score * 10 + this.numOfPrfrdCond;
    score = score * 10 - this.numOfXs;
    // Occams Razor적용
    //score = score * 100 - size;
    return score;
  }
 
 
  /**
   * <pre>
   * 어절간 결합시에만 호출
   * 선호 조건에 대한 검사를 하지 않고 반환함.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 20
   * @return
   */
  int getScore2()
  {
    score = this.realDicLen + this.candDicLen;
    score = score * 100 - numOfSpace;
    score = score * 10 + this.realDicLen;
    score = score * 1000 - this.numOfXs;
    // Occams Razor적용
    //score = score * 100 - size;
    return score;
  }


  /**
   * <pre>
   * 사전 어휘 길이를 반환한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 21
   * @return
   */
  int getDicLenOnlyReal()
  {
    return this.realDicLen;
  }


  /**
   * <pre>
   * 종결되지 않은 것까지 사전 어휘로 고려하여 사전 어휘 길이를 반환한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 21
   * @return
   */
  int getDicLenWithCand()
  {
    return this.candDicLen + this.realDicLen;
  }


  /**
   * <pre>
   * 후보 사전어의 길이를 반환한다.
   * </pre>
   * @author  therocks
   * @since  2007. 7. 25
   * @return
   */
  int getDicLenOnlyCand()
  {
    return this.candDicLen;
  }


  /**
   * <pre>
   * 시작이나 끝이 불완전한 품사이면 candDicLen을 가지고, 이로 인해 불완전한 후보로 인식
   * </pre>
   * @author  therocks
   * @since  2007. 7. 21
   * @return
   */
  boolean isComplete()
    throws Exception
  {
    return candDicLen == 0;
  }


  /**
   * <pre>
   * 사전어와 비사전어의 길이를 계산한다.
   * 오버헤드를 유발하는 함수이므로 꼭 필요한 경우에만 호출한다.
   * derive() 다음에만 호출한다!!
   * </pre>
   * @author  therocks
   * @since  2007. 7. 23
   */
  private void calcDicLen()
  {
    // 형태소 수 설정
    byte size = (byte) size();
    numOfSpace = 0;

    // 사전 길이 초기화
    realDicLen = 0;
    candDicLen = 0;
    numOfXs = 0;
   
    int expIdx = 0;
    int nrDicLen = 0;
    boolean hasPreWord = false, hasJo = false;
    boolean hasStem = false, hasEP = false, hasEM = false;

    Morpheme mp = null;
    for( int i = 0, stop = size + 1; i < stop; i++ ) {
      if( i < size )
        mp = get(i);
      else
        mp = null;
     
      if( mp == null || mp instanceof MorphemeSpace ) {
        // [어간(+선어말어미)+어미] 완료 확인
        boolean complete = !hasEP || !(hasStem ^ hasEM);
        // [체언(관형사,부사)+조사] 완료 확인
        complete = complete && (!hasJo || hasPreWord);

        // 사전어, 비사전어 설정
        // 줄임말인 경우에는 줄임말 앞부분의 완료 여부에 따라서 완료 여부 설정
        if( complete ) {
          realDicLen += expList.get(expIdx).length() - nrDicLen;
        } else {
          candDicLen += expList.get(expIdx).length() - nrDicLen;
        }

        if( mp == null ) {
          // 미등록어 + 조사인 경우는 미등록어를 후보 사전어로 취급할 수 있도록 한다.
          // 사전어 길이에서 1을 빼줌으로 다른 완전히 사전어보다는 우선순위를 낮추어준다.
          if( size == 2 && lastMorp.isTagOf(POSTag.J) && firstMorp.isTag(POSTag.UN) ) {
            candDicLen += nrDicLen - 1;
          }
        }
        // 공백 크기 증가시키기
        else {
          numOfSpace++;
        }

        // 다음을 위해 초기화
        hasPreWord = false;
        hasJo = false;
        hasStem = false;
        hasEP = false;
        hasEM = false;
        nrDicLen = 0;

        // 표제어 인덱스 증가
        expIdx++;
      }
      // 띄어쓰기를 기준으로 표현형에 대한 사전어 여부 처리
      else {
        // 어간존재 확인
        if( mp.isTagOf(POSTag.V) ) {
          hasStem = true;
          hasPreWord = true;
          // 서술격 조사는 어간이며, 조사로 처리
          if( mp.isTag(POSTag.VCP) ) hasJo = true;
        }
        // 선어말 어미 존재 확인
        else if( mp.isTagOf(POSTag.EP) ) {
          hasEP = true;
          hasPreWord = true;
        }
        // 어미 존재 확인
        else if( mp.isTagOf(POSTag.EM) ) {
          hasEM = true;
          hasPreWord = true;
          // 명사형으로 종결이면 조사 앞말 출현으로 설정
          if( mp.isTag(POSTag.ETN) ) hasPreWord = true;
        }
        // 조사 설정
        else if( mp.isTagOf(POSTag.J) ) {
          hasJo = true;
        }
        // 미등록어인 경우
        else if( mp.isTag(POSTag.UN) ) {
          hasPreWord = true;
          nrDicLen += mp.string.length();
        }
        // 앞말 존재로 설정
        else {
          if( mp.isTagOf(POSTag.XP | POSTag.XS) ) numOfXs++;
          hasPreWord = true;
        }
      }
    }

    // hashCode 계산
    calcHashCode();
  }
 
 
  /**
   * 띄어쓰기 없이 접속 가능한 품사 정보
   */
  public static final String  DLMT_ATL  = "#";
  /**
   * 현재 후보가 가지는 조건 정보
   */
  public static final String  DLMT_HCL  = "&";
  /**
   * 접속할 때 확인해야하는 조건 정보 뒤로 맞출 때 확인해야함.
   */
  public static final String  DLMT_BCL  = "~";
  /**
   * 접속할 때 확인해야하는 조건 정보
   */
  public static final String  DLMT_CCL  = "@";
  /**
   * 접합 배재 조건 정보
   */
  public static final String  DLMT_ECL  = "¬";
  /**
   * 띄어쓰기 시 이전에 나오는 품사 정보
   */
  public static final String  DLMT_PCL  = "%";
  /**
   * 복합명사 목록
   */
  public static final String  DLMT_CNL  = "$";


  /**
   * <pre>
   * 사전의 후보 분석 결과 문자열로부터 객체를 생성한다.
   * 공백에 대한 처리를 추가하기 위하여 수정 2007-07-19
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @param exp
   * @param source
   */
  public static MCandidate create(String exp, String source)
  {
    MCandidate mCandidate = new MCandidate();
    mCandidate.setExp(exp);
    StringTokenizer st = new StringTokenizer(source, "[]", false);

    // 기분석 결과 저장
    String token = null, infos = "";
    String[] arr = null;
    for( int i = 0; st.hasMoreTokens(); i++ ) {
      token = st.nextToken();
      if( i == 0 ) {
        arr = token.split("\\+");
        for( int j = 0; j < arr.length; j++ ) {
          // 앞 뒤조건 정보들을 가지는 공백 문자열 생성
          if( arr[j].startsWith(" ") ) {
            mCandidate.add(new MorphemeSpace(arr[j]));
            mCandidate.expList.add(0, "_");
          }
          // 일반적인 형태소 분석 결과 저장
          else {
            mCandidate.add(Morpheme.create(arr[j]));
          }
        }
      } else {
        infos = token;
      }
    }


    // 부가 정보들에 대한 처리 수행
    st = new StringTokenizer(infos, "*" + DLMT_ATL + DLMT_BCL + DLMT_HCL + DLMT_CCL + DLMT_ECL + DLMT_PCL, true);
    while(st.hasMoreTokens()) {
      token = st.nextToken();
      // 접속 가능한 품사 정보
      if(token.equals(DLMT_ATL)) {
        token = st.nextToken().trim();
        token = token.substring(1, token.length() - 1);
        mCandidate.addApndblTags(token.split(","));
      }
      // 현재 후보가 가진 접속 조건
      else if(token.equals(DLMT_HCL)) {
        token = st.nextToken().trim();
        token = token.substring(1, token.length() - 1);
        mCandidate.addHavingConds(token.split(","));
      }
      // 접속 확인 조건
      else if(token.equals(DLMT_CCL)) {
        token = st.nextToken().trim();
        token = token.substring(1, token.length() - 1);
        mCandidate.addChkConds(token.split(","));
      }
      // 접속 배제 조건
      else if(token.equals(DLMT_ECL)) {
        token = st.nextToken().trim();
        token = token.substring(1, token.length() - 1);
        mCandidate.addExclusionConds(token.split(","));
      }
      // 띄어쓰기 시 선호 조건
      else if(token.equals(DLMT_PCL)) {
        token = st.nextToken().trim();
        token = token.substring(1, token.length() - 1);
        mCandidate.addPreferedConds(token.split(","));
      }
    }
    mCandidate.initConds(exp);
    mCandidate.calcDicLen();
    return mCandidate;
  }


  /**
   * <pre>
   * 분석 후보에 대한 조건을 받아들여서 생성
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @param exp
   * @param analResult
   * @param atl
   * @param hcl
   * @param ccl
   * @param ecl
   * @param pcl
   * @return
   */
  public static MCandidate create(String exp, String analResult, String atl, String hcl, String ccl, String ecl, String pcl)
  {
    MCandidate mCandidate = new MCandidate();
    mCandidate.setExp(exp);

    // 기분석 결과 저장
    String[] arr = analResult.split("\\+");
    for( int j = 0; j < arr.length; j++ ) {
      // 앞 뒤조건 정보들을 가지는 공백 문자열 생성
      if( arr[j].startsWith(" ") ) {
        mCandidate.add(new MorphemeSpace(arr[j]));
        mCandidate.expList.add(0, "");
      }
      // 일반적인 형태소 분석 결과 저장
      else {
        mCandidate.add(Morpheme.create(arr[j]));
      }
    }
    // 조건 초기화
    mCandidate.initConds(exp);
    mCandidate.calcDicLen();

    // 접속 가능한 품사 정보
    if( Util.valid(atl) ) mCandidate.addApndblTags(atl.split(","));

    // 현재 후보가 가진 접속 조건
    if( Util.valid(hcl) ) mCandidate.addHavingConds(hcl.split(","));

    // 접속할 때 확인해야 하는 조건
    if( Util.valid(ccl) ) mCandidate.addChkConds(ccl.split(","));
   
    // 접속할 때 배제해야 하는 조건
    if( Util.valid(ecl) ) mCandidate.addExclusionConds(ecl.split(","));

    // 띄어쓰기 시 선호되는 조건 정보
    if( Util.valid(pcl) ) mCandidate.addPreferedConds(pcl.split(","));
   
    return mCandidate;
  }


  /**
   * <pre>
   * 기분석 후보 정보를 반환한다.
   * 분석 사전에서 { } 내에 들어갈 정보를 반환해준다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 4
   * @return
   */
  public String toString()
  {
    return getString();
  }


  /**
   * <pre>
   * 기분석 후보 정보를 반환한다.
   * 분석 사전에서 { } 내에 들어갈 정보를 반환해준다.
   * </pre>
   * @author  therocks
   * @since  2009. 09. 29
   * @return
   */
  public String getString()
  {
    StringBuffer sb = new StringBuffer();
   
    sb.append(getScore());
    sb.append("\t" + realDicLen);
    sb.append("\t" + candDicLen);
    sb.append("\t" + numOfSpace);
    sb.append("\t" + numOfPrfrdCond);
    sb.append("\t" + numOfXs);
    sb.append("\t" + size());
    sb.append("\t" + scoreOfBestMC);
    sb.append("\t" + lnprOfBestMC+"\t");
    sb.append("\t" + lnprOfSpacing+"\t");

    // 형태소 분석 결과
    sb.append("[" + super.toString() + "]");

    // 접속 가능한 품사 정보
    String temp = POSTag.getZipTagStr(atlEnc);
    if( temp != null ) sb.append(DLMT_ATL + "(" + temp + ")");

    // 현재 후보가 가진 접속 조건
    temp = Condition.getCondStr(hclEnc);
    if( temp != null ) sb.append(DLMT_HCL + "(" + temp + ")");
   
    // 뒷방향 접속 조건 확인
    temp = Condition.getCondStr(bclEnc);
    if( temp != null ) sb.append(DLMT_BCL + "(" + temp + ")");

    // 접속할 때 확인해야 하는 조건
    temp = Condition.getCondStr(cclEnc);
    if( temp != null ) sb.append(DLMT_CCL + "(" + temp + ")");

    // 접속할 때 배제해야 하는 조건
    temp = Condition.getCondStr(eclEnc);
    if( temp != null ) sb.append(DLMT_ECL + "(" + temp + ")");
   
    // 띄어쓰기 시 선호되는 조건 정보
    temp = Condition.getCondStr(pclEnc);
    if( temp != null ) sb.append(DLMT_PCL + "(" + temp + ")");
    sb.append("\t" + hashCode);
   
    sb.append("\t" + this.expList);
    return sb.toString();
  }


  /**
   * <pre>
   * 기본적으로 설정된 조건 외에 추가적으로 설정된 조건만 포함한 문자열을 생성하여 반환한다.
   * </pre>
   * @author  therocks
   * @since  2009. 09. 30
   * @return
   */
  public String getSmplDicStr(String compResult)
  {
    StringBuffer sb = new StringBuffer();

    final long mask = 0xffffffffffffffffl;
    long basicATL = getBasicApndblTags();
    long basicHCL = getBasicHavingConds() | getBasicPhonemeConds(getExp());
    long basicPCL = getBasicPreferedConds();

    // 형태소 분석 결과
    sb.append(super.getSmplStr2());

    StringBuffer sb2 = new StringBuffer();
    // 접속 가능한 품사 정보
    String temp = POSTag.getZipTagStr(atlEnc & (mask ^ basicATL));
    if( temp != null ) sb2.append(DLMT_ATL + "(" + temp + ")");

    // 현재 후보가 가진 접속 조건
    temp = Condition.getCondStr(hclEnc & (mask ^ basicHCL));
    if( temp != null ) sb2.append(DLMT_HCL + "(" + temp + ")");

    // 접속할 때 확인해야 하는 조건
    temp = Condition.getCondStr(cclEnc);
    if( temp != null ) sb2.append(DLMT_CCL + "(" + temp + ")");
   
    // 접속할 때 배제해야 하는 조건
    temp = Condition.getCondStr(eclEnc);
    if( temp != null ) sb2.append(DLMT_ECL + "(" + temp + ")");

    // 띄어쓰기 시 선호되는 조건 정보
    temp = Condition.getCondStr(pclEnc & (mask ^ basicPCL));
    if( temp != null ) sb2.append(DLMT_PCL + "(" + temp + ")");

    // 복합명사 분해 결과
    if( Util.valid(compResult) ) sb2.append(DLMT_CNL + "(" + compResult + ")");

    if( sb2.length() > 0 ) {
      sb.append(";");
      sb.append(sb2);
    }

    return sb.toString();
  }
 
 
  public String getRawDicStr()
  {
    StringBuffer sb = new StringBuffer();

    final long mask = 0xffffffffffffffffl;
    long basicATL = getBasicApndblTags();
    long basicHCL = getBasicHavingConds() | getBasicPhonemeConds(getExp());
    long basicPCL = getBasicPreferedConds();

    String temp = null;
    // 형태소 분석 결과
    sb.append(getExp() + ":{[" + super.getSmplStr2() + "]");

    // 접속 가능한 품사 정보
    if( (temp = POSTag.getZipTagStr(atlEnc & (mask ^ basicATL))) != null ) sb.append(DLMT_ATL + "(" + temp + ")");
    // 현재 후보가 가진 접속 조건
    if( (temp = Condition.getCondStr(hclEnc & (mask ^ basicHCL))) != null ) sb.append(DLMT_HCL + "(" + temp + ")");
    // 접속할 때 확인해야 하는 조건
    if( (temp = Condition.getCondStr(cclEnc)) != null ) sb.append(DLMT_CCL + "(" + temp + ")");
    // 접속할 때 배제해야 하는 조건
    temp = Condition.getCondStr(eclEnc);
    if( (temp = Condition.getCondStr(eclEnc)) != null ) sb.append(DLMT_ECL + "(" + temp + ")");
    // 띄어쓰기 시 선호되는 조건 정보
    if( (temp = Condition.getCondStr(pclEnc & (mask ^ basicPCL))) != null ) sb.append(DLMT_PCL + "(" + temp + ")");
    sb.append("}");

    return sb.toString();
  }
 
 
  public String toSimpleStr()
  {
    return super.toString();
  }


  /**
   * <pre>
   * 형태소 분석 정보에 부가 정보를 encoding된 상태로 추가한다.
   * </pre>
   * @author  therocks
   * @since  2007. 6. 11
   * @return
   */
  String getEncStr()
  {
    StringBuffer sb = new StringBuffer();
    sb.append(super.getEncStr());
    sb.append("!" + atlEnc);
    sb.append("!" + hclEnc);
    sb.append("!" + cclEnc);
    sb.append("!" + eclEnc);
    sb.append("!" + pclEnc);
    return sb.toString();
  }


  /**
   * <pre>
   * 하나의 형태소의 품사만 제외하고 나머지가 다 동일할 경우 품사 정보를 합쳐준다.
   * </pre>
   * @author  therocks
   * @since  2009. 10. 15
   * @param mc 통합할 대상 분석 후보
   * @return 정상적으로 합쳐진 경우에는 true를 반환하고, 합쳐지지 않은 경우에는 false를 반환한다.
   */
  boolean merge(MCandidate mc)
  {
    int size = size();
    if( size != mc.size() ) return false;
    if( atlEnc != mc.atlEnc ) return false;
    if( hclEnc != mc.hclEnc ) return false;
    if( cclEnc != mc.cclEnc ) return false;
    if( eclEnc != mc.eclEnc ) return false;
    if( pclEnc != mc.pclEnc ) return false;

    Morpheme mp1 = null, mp2 = null, catchedMp1 = null, catchedMp2 = null;

    for( int i = 0; i < size; i++ ) {
      mp1 = get(i);
      mp2 = mc.get(i);

      if( !mp1.string.equals(mp2.string) ) return false;
      if( mp1.infoEnc != mp2.infoEnc ) {
        if( catchedMp1 != null ) return false;
        catchedMp1 = mp1;
        catchedMp2 = mp2;
      }
    }

    if( catchedMp1 == null ) return true;

    catchedMp1.infoEnc |= catchedMp2.infoEnc;

    return true;
  }


  /**
   * <pre>
   * 어절간 근접 확률만을 이용하여 최적 조합을 찾는다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 19
   * @param mcPrev
   */
  public void setBestPrevMC(MCandidate mcPrev)
  {
    long prevTag = POSTag.BOS;
    int prevBestScore = 0;
    float prevBestProb = 0;
    int newPrfrdCond = 0;
    boolean apndbl = false;
   
    if( mcPrev != null ) {
      prevTag = mcPrev.lastMorp.getTagNum();
      prevBestScore = mcPrev.scoreOfBestMC;
      prevBestProb = mcPrev.lnprOfBestMC;
     
      // 띄어쓰기 없이 결합 가능하고, 띄어쓰기 불가능한 경우에는 띄어쓰기 없이 확률 계산
      apndbl = mcPrev.isApndbl(this) && !mcPrev.isApndblWithSpace(this);
     
      // 선호 점수 부여
      if( !apndbl ) {
        newPrfrdCond = Long.bitCount(mcPrev.hclEnc & this.pclEnc);
      }
    }
   
    // 점수 계산
    int newBestScore = prevBestScore + getScore2() + newPrfrdCond * 10;
   
    //float newProb = calcProb(prevTag, apndbl);      // 모델에 나온 확률 계산 법
    float newProb = calcProbInter(prevTag);          // 근접 확률만을 계산
    float newBestProb = prevBestProb +  newProb;
   
    if( prevBestMC == null ) {
      scoreOfBestMC = newBestScore;
      lnprOfBestMC = newBestProb;
      prevBestMC = mcPrev;
      numOfPrfrdCond = (byte)newPrfrdCond;
    } else if( newBestProb > lnprOfBestMC + 10 ) {
      scoreOfBestMC = newBestScore;
      lnprOfBestMC = newBestProb;
      prevBestMC = mcPrev;
      numOfPrfrdCond = (byte) newPrfrdCond;
    } else if( newBestScore > scoreOfBestMC ) {
      scoreOfBestMC = newBestScore;
      lnprOfBestMC = newBestProb;
      prevBestMC = mcPrev;
      numOfPrfrdCond = (byte)newPrfrdCond;
    } else if( newBestScore == scoreOfBestMC
        &&  newBestProb > lnprOfBestMC )
    {
      scoreOfBestMC = newBestScore;
      lnprOfBestMC = newBestProb;
      prevBestMC = mcPrev;
      numOfPrfrdCond = (byte) newPrfrdCond;
    }
  }


  /**
   * <pre>
   * 어절간 인접 관계만을 고려하여 확률을 계산한다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 20
   * @param interPrevTag
   * @return
   */
  float calcProbInter(long interPrevTag)
  {
    float newProb = 0;
    // 기호가 분리되어 같은 어절에 있음에도 어절간 확률로 처리되는 것을 방지
    if( firstMorp.isTagOf(POSTag.S)) {
      newProb += PDDictionary.getProbIntraBi(interPrevTag, firstMorp.string, firstMorp.getTagNum());
    } else {
      newProb += PDDictionary.getProbInterBi(interPrevTag, firstMorp.string, firstMorp.getTagNum());
    }
    newProb += PDDictionary.getProbUni(firstMorp.string, firstMorp.getTagNum());
    newProb -= PDDictionary.getProbUni(null, interPrevTag);
   
    // TODO remove if there is nothing to debug related to probability calculation
    //System.out.println(POSTag.getTag(interPrevTag) + "\t" + firstMorp + "\t" + newProb);
   
    return newProb;
  }


  /**
   * <pre>
   *
   * </pre>
   * @author  therocks
   * @since  2009. 10. 16
   * @param interPrevTag
   * @param isAndbl
   * @return 이전 결합조건을 확인한 확률 값
   */
  public float calcProb(long interPrevTag, boolean isAndbl)
  {
    // 확률값 초기화
    float prob = 0;

    long intraPrevTag = 0;
    // sapcing없이 붙여쓰기가 가능한 경우에 대한 처리
    if( isAndbl ) {
      intraPrevTag = interPrevTag;
      interPrevTag = 0;
    }

    Morpheme mp = null;
    for( int i = 0, size = size(); i < size; i++ ) {
      mp = get(i);
      // 띄어쓰기할 때의 확률값으로 처리
      if( mp instanceof MorphemeSpace ) {
        interPrevTag = intraPrevTag;
        intraPrevTag = 0;
      }
      // 확률값 계산
      else {
        // 형태소 출현 확률
        prob += PDDictionary.getProbUni(mp.string, mp.getTagNum());

        // 형태소 인접 확률 (어절간)
        if( interPrevTag != 0 ) {
          prob += PDDictionary.getProbInterBi(mp.getTagNum(), mp.string, interPrevTag);
          // 이전 형태소 출현 확률
          prob -= PDDictionary.getProbUni(null, interPrevTag);
          interPrevTag = 0;
        }
        // 형태소 인접 확률 (어절내)
        else if( intraPrevTag != 0 ) {
          prob += PDDictionary.getProbIntraBi(mp.getTagNum(), mp.string, intraPrevTag);
          // 이전 형태소 출현 확률
          prob -= PDDictionary.getProbUni(null, intraPrevTag);
          intraPrevTag = mp.getTagNum();
        }
      }
    }
   
    return prob;
  }


  /**
   * @return the scoreOfBestMC
   */
  public int getScoreOfBestMC()
  {
    return scoreOfBestMC;
  }


  /**
   * @return the lnprOfBestMC
   */
  public float getLnprOfBestMC()
  {
    return lnprOfBestMC;
  }


  /**
   * @return the sizeOfBestMC
   */
  public byte getSizeOfBestMC()
  {
    return sizeOfBestMC;
  }


  /**
   * @return the prevBestMC
   */
  public MCandidate getPrevBestMC()
  {
    return prevBestMC;
  }


  /**
   * @return the realDicLen
   */
  public byte getRealDicLen()
  {
    return realDicLen;
  }


  /**
   * @param realDicLen the realDicLen to set
   */
  public void setRealDicLen(byte realDicLen)
  {
    this.realDicLen = realDicLen;
  }


  /**
   * @return the candDicLen
   */
  public byte getCandDicLen()
  {
    return candDicLen;
  }


  /**
   * @param candDicLen the candDicLen to set
   */
  public void setCandDicLen(byte candDicLen)
  {
    this.candDicLen = candDicLen;
  }

 
  /**
   * <pre>
   * 선호 조건의 수를 1 감소시킨다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 21
   */
  public void decreaseNumOfPrfrdCond()
  {
    numOfPrfrdCond--;
  }


  /**
   * <pre>
   * 첫번째 형태소가 주어진 품사 중 하나인지를 확인하여 반환한다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 21
   * @param tagEnc
   * @return
   */
  public boolean isFirstTagOf(long tagEnc)
  {
    return firstMorp.isTagOf(tagEnc);
  }


  /**
   * <pre>
   * 한글 이외것들의 조합인지 확인한다.
   * </pre>
   * @author  Dongjoo
   * @since  2009. 10. 26
   * @return
   */
  public boolean isNotHangul()
  {
    return !lastMorp.isCharSetOf(CharSetType.HANGUL);
  }


  /**
   * @return the lnprOfSpacing
   */
  public float getLnprOfSpacing()
  {
    return lnprOfSpacing;
  }


  /**
   * @param lnprOfSpacing the lnprOfSpacing to set
   */
  public void setLnprOfSpacing(float lnprOfSpacing)
  {
    this.lnprOfSpacing = lnprOfSpacing;
  }

}
TOP

Related Classes of org.snu.ids.ha.ma.MCandidate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.