SharpICTCLAS分词系统简介(3)DynamicArray

来源:互联网 发布:南海舰队编制 知乎 编辑:程序博客网 时间:2024/05/22 09:04

原文地址:http://www.cnblogs.com/zhenyulu/articles/668695.html

从前文可以看出,ICTCLAS中DynamicArray类在初步分词过程中起到了至关重要的所用,而ICTCLAS中DynamicArray类的实现比较复杂,可以说是包罗万象,在一个GetElement方法就综合考虑了1)row优先排序的链表;2)col优先排序的链表;3)当nRow为-1时的行为;4)当nCol为-1时的行为;5)当nRow与nCol都不为-1时的行为 (可以参考本人的《天书般的ICTCLAS分词系统代码(一)》一文)。

为了简化编程接口,并将纠缠不清的代码逻辑剥离开来,我重新设计了DynamicArray类,利用三个类实现原有一个类的功能。具体改造包括:1) 将DynamicArray类做成一个抽象父类,实现一些公共功能;2)设计了RowFirstDynamicArray类与ColumnFirstDynaimcArray类作为DynamicArray的子类,分别实现row优先排序和col优先排序的DynamicArray。2) 在牺牲有限性能的同时力争大幅度简化代码的复杂度,提高可读性。

具体实现如下:

1、DynamicArray链表结点的定义

为了使得DynamicArray更具有通用性,使用了范型方式定义了链表的结点,代码如下:

 Copy Code
DynamicArray链表结点的定义
public class ChainItem<T> 

   public int row; 
   public int col; 
   public T Content; 
   public ChainItem<T> next; 
}

2、DynamicArray类

DynamicArray类是一个抽象类,主要为RowFirstDynamicArray类与ColumnFirstDynaimcArray类提供公共的基础功能,例如查找行、列值为nRow, nCol的结点等。同时,该类将插入一新结点的方法设计成抽象方法,需要具体类根据row优先排序还是col优先排序自行决定具体实现。DynamicArray类的代码实现如下(应当说非常简单):

 Copy Code
DynamicArray.cs 程序
public abstract class DynamicArray<T> 

   protected ChainItem<T> pHead;  //The head pointer of array chain 
   public int ColumnCount, RowCount;  //The row and col of the array 

   public DynamicArray() 
   { 
      pHead = null
      RowCount = 0; 
      ColumnCount = 0; 
   } 

   public int ItemCount 
   { 
      get 
      { 
         ChainItem<T> pCur = pHead; 
         int nCount = 0; 
         while (pCur != null
         { 
            nCount++; 
            pCur = pCur.next; 
         } 
         return nCount; 
      } 
   } 

   //==================================================================== 
   // 查找行、列值为nRow, nCol的结点 
   //==================================================================== 
   public ChainItem<T> GetElement(int nRow, int nCol) 
   { 
      ChainItem<T> pCur = pHead; 

      while (pCur != null && !(pCur.col == nCol && pCur.row == nRow)) 
         pCur = pCur.next; 

      return pCur; 
   } 

   //==================================================================== 
   // 设置或插入一个新的结点 
   //==================================================================== 
   public abstract void SetElement(int nRow, int nCol, T content); 

   //==================================================================== 
   // Return the head element of ArrayChain 
   //==================================================================== 
   public ChainItem<T> GetHead() 
   { 
      return pHead; 
   } 

   //==================================================================== 
   //Get the tail Element buffer and return the count of elements 
   //==================================================================== 
   public int GetTail(out ChainItem<T> pTailRet) 
   { 
      ChainItem<T> pCur = pHead, pPrev = null
      int nCount = 0; 
      while (pCur != null
      { 
         nCount++; 
         pPrev = pCur; 
         pCur = pCur.next; 
      } 
      pTailRet = pPrev; 
      return nCount; 
   } 

   //==================================================================== 
   // Set Empty 
   //==================================================================== 
   public void SetEmpty() 
   { 
      pHead = null
      ColumnCount = 0; 
      RowCount = 0; 
   } 

   public override string ToString() 
   { 
      StringBuilder sb = new StringBuilder(); 

      ChainItem<T> pCur = pHead; 

      while (pCur != null
      { 
         sb.Append(string.Format("row:{0,3},  col:{1,3},  ", pCur.row, pCur.col)); 
         sb.Append(pCur.Content.ToString()); 
         sb.Append("\r\n"); 
         pCur = pCur.next; 
      } 

      return sb.ToString(); 
   } 
}

3、RowFirstDynamicArray类的实现

RowFirstDynamicArray类主要实现了row优先排序的DynamicArray,里面包含了两个方法:GetFirstElementOfRow(获取row为nRow的第一个元素)和SetElement方法。其中GetFirstElementOfRow有两个重载版本。

这等价于将原有ICTCLAS中GetElement方法拆分成了三个方法,如果算上重载版本的话共五个方法,它们分别是:1)获取行、列值为nRow, nCol的结点,此方法由DynamicArray类实现;2)对于Row优先排序的链表而言,单独提供了GetFirstElementOfRow方法。3)对于Column优先排序的链表而言,单独提供了GetFirstElementOfColumn方法。

RowFirstDynamicArray类的实现如下:

 Copy Code
RowFirstDynamicArray.cs 程序
public class RowFirstDynamicArray<T> : DynamicArray<T> 

   //==================================================================== 
   // 查找行为 nRow 的第一个结点 
   //==================================================================== 
   public ChainItem<T> GetFirstElementOfRow(int nRow) 
   { 
      ChainItem<T> pCur = pHead; 

      while (pCur != null && pCur.row != nRow) 
         pCur = pCur.next; 

      return pCur; 
   } 

   //==================================================================== 
   // 从 startFrom 处向后查找行为 nRow 的第一个结点 
   //==================================================================== 
   public ChainItem<T> GetFirstElementOfRow(int nRow, ChainItem<T> startFrom) 
   { 
      ChainItem<T> pCur = startFrom; 

      while (pCur != null && pCur.row != nRow) 
         pCur = pCur.next; 

      return pCur; 
   } 

   //==================================================================== 
   // 设置或插入一个新的结点 
   //==================================================================== 
   public override void SetElement(int nRow, int nCol, T content) 
   { 
      ChainItem<T> pCur = pHead, pPre = null, pNew;  //The pointer of array chain 

      if (nRow > RowCount)//Set the array row 
         RowCount = nRow; 

      if (nCol > ColumnCount)//Set the array col 
         ColumnCount = nCol; 

      while (pCur != null && (pCur.row < nRow || (pCur.row == nRow && pCur.col < nCol))) 
      { 
         pPre = pCur; 
         pCur = pCur.next; 
      } 

      if (pCur != null && pCur.row == nRow && pCur.col == nCol)//Find the same position 
         pCur.Content = content;//Set the value 
      else 
      { 
         pNew = new ChainItem<T>();//malloc a new node 
         pNew.col = nCol; 
         pNew.row = nRow; 
         pNew.Content = content; 

         pNew.next = pCur; 

         if (pPre == null)//link pNew after the pPre 
            pHead = pNew; 
         else 
            pPre.next = pNew; 
      } 
   } 
}

有关ColumnFirstDynamicArray类的实现大同小异,这里就不再提供代码了。我们此时可以对比一下原有ICTCLAS中GetElement的实现:

 Copy Code
DynamicArray.cpp
ELEMENT_TYPE CDynamicArray::GetElement(int nRow, int nCol, PARRAY_CHAIN pStart, 
  PARRAY_CHAIN *pRet) 

  PARRAY_CHAIN pCur = pStart; 
  if (pStart == 0) 
    pCur = m_pHead; 
  if (pRet != 0) 
    *pRet = NULL; 
  if (nRow > (int)m_nRow || nCol > (int)m_nCol) 
  //Judge if the row and col is overflow 
    return INFINITE_VALUE; 
  if (m_bRowFirst
  { 
    while (pCur != NULL && (nRow !=  - 1 && (int)pCur->row < nRow || (nCol !=   
      - 1 && (int)pCur->row == nRow && (int)pCur->col < nCol))

    { 
      if (pRet != 0) 
        *pRet = pCur; 
      pCur = pCur->next; 
    } 
  } 
  else 
  { 
    while (pCur != NULL && (nCol !=  - 1 && (int)pCur->col < nCol || ((int)pCur 
      ->col == nCol && nRow !=  - 1 && (int)pCur->row < nRow))

    { 
      if (pRet != 0) 
        *pRet = pCur; 
      pCur = pCur->next; 
    } 
  } 
  if (pCur != NULL && ((int)pCur->row == nRow || nRow ==  - 1) && ((int)pCur 
    ->col == nCol || nCol ==  - 1)

  //Find the same position 
  { 
    //Find it and return the value 
    if (pRet != 0) 
      *pRet = pCur; 
    return pCur->value
  } 
  return INFINITE_VALUE; 
}

可以看出,将原有GetElement方法拆分成3个方法后,代码得到大大简化,而且逻辑更为清晰了。

3、性能与代码可读性的权衡

DynamicArray类为了确保代码的清晰可读,在某些地方做了些调整,让我们对比一下SharpICTCLAS与ICTCLAS中在这方面的不同考虑。下面的代码演示了GetFirstElementOfRow方法在两者之间的不同之处(我特意对ICTCLAS代码做了逻辑上的简化):

 Copy Code
程序
//==================================================================== 
// SharpICTCLAS 中的查找行为 nRow 的第一个结点 
//==================================================================== 
public ChainItem<T> GetFirstElementOfRow(int nRow) 

   ChainItem<T> pCur = pHead; 

   while (pCur != null && pCur.row != nRow) 
      pCur = pCur.next; 

   return pCur; 


//==================================================================== 
// ICTCLAS 中的查找行为 nRow 的第一个结点 
//==================================================================== 
... GetElement(int nRow, int nCol, PARRAY_CHAIN pStart, PARRAY_CHAIN *pRet)  
{  
  PARRAY_CHAIN pCur = pStart;  

  while (pCur != NULL && (pCur->row < nRow || (pCur->row == nRow && pCur->col < nCol)))  
  {  
    if (pRet != 0)  
      *pRet = pCur;  
    pCur = pCur->next;  
  }  

  if (pCur != NULL && pCur->row == nRow && pCur->col == nCol)  
  {  
    if (pRet != 0)  
      *pRet = pCur;  
    return pCur->value;  
  }  
  //...... 
}

从上面代码中可以看出,原有ICTCLAS代码充分考虑到DynamicArray是一个排序链表,因此仅仅在pCur->row < nRow与pCur->col < nCol范围内检索,如果找到了“pCur->row == nRow && pCur->col == nCol”,那么再去做该做的事情。

而SharpICTCLAS中,判断条件仅为“pCur != null && pCur.row != nRow”,这意味着如果你要找的nRow不再该链表中,则会来个“完全遍历”,搜索范围似乎太大了。

不过出于以下几点考虑我还是采用了这种表示方式:

1)汉语中的一句话不会太长,这意味着链表长度不会很长,即使来个“完全遍历”也不会牺牲多少时间。

2)毕竟要找的nRow不在该链表中的可能性不大,出现“完全遍历”的机会也不多。

3)原有ICTCLAS虽然在搜索范围内下了翻功夫,但为了确保pRet变量得到赋值,循环体内部多次执行了“if (pRet != 0)”的判断,这从性能角度上说得不偿失。

4)原有ICTCLAS为了缩小搜索范围确增加了条件判断次数“pCur != NULL && (pCur->row < nRow || (pCur->row == nRow && pCur->col < nCol))”,而由此带来的性能损失不得不考虑一下。

正因为以上几点考虑,所以在SharpICTCLAS中采用了这种简单而且并不见得低效的方式取代原有的GetElement方法。

  • 小结

SharpICTCLAS重新设计了DynamicArray类,力争简化原有设计中复杂的代码逻辑,应当说效果比较明显。即便有性能损失,那也是微不足道的,权衡利弊,我选择了走简单的代码这条路。