First we try, then we trust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

从前文可以看出,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更具有通用性,使用了范型方式定义了链表的结点,代码如下:

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类的代码实现如下(应当说非常简单):

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类的实现如下:

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的实现:

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代码做了逻辑上的简化):

程序
//====================================================================
// 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类,力争简化原有设计中复杂的代码逻辑,应当说效果比较明显。即便有性能损失,那也是微不足道的,权衡利弊,我选择了走简单的代码这条路。

 

posted on 2007-03-08 23:13  吕震宇  阅读(6047)  评论(4编辑  收藏  举报