海闊天空

          I'm on my way!
          隨筆 - 17, 文章 - 69, 評論 - 21, 引用 - 0
          數據加載中……

          文本處理(一)狀態機(2)

          系統程序員成長計劃-文本處理(一)

          狀態機(2)

          o 用有窮狀態機解一道面試題。

          剛畢業的時候,我到一家外企面試,面試題里有這樣一道題:

          統計一篇英文文章里的單詞個數。

          有多種方法可以解這道題,這里我們選擇用有窮狀態機來解,做法如下:

          先把這篇英文文章讀入到一個緩沖區里,讓一個指針從緩沖區的頭部一直移到緩沖區的尾部,指針會處于兩種狀態:“單詞內”或“單詞外”,加上后面提到的初始狀態和接受狀態,就是有窮狀態機的狀態集。緩沖區中的字符集合就是有窮狀態機的字母表。

          如果當前狀態為“單詞內”,移到指針時,指針指向的字符是非單詞字符(如標點和空格),那狀態會從“單詞內”轉換到“單詞外”。如果當前狀態為“單 詞外”, 移到指針時,指針指向的字符是單詞字符(如字母),那狀態會從“單詞外”轉換到“單詞內”。這些轉換規則就是狀態轉換函數。

          指針指向緩沖區的頭部時是初始狀態。

          指針指向緩沖區的尾部時是接受狀態。

          每次當狀態從“單詞內”轉換到“單詞外”時,單詞計數增加一。
          這個有窮狀態機的圖形表示如下:

          下面我們看看程序怎么寫:

          int count_word(const char* text)

          {

          /*定義各種狀態,我們不關心接受狀態,這里可以不用定義。*/

          enum _State

          {

          STAT_INIT,

          STAT_IN_WORD,

          STAT_OUT_WORD,

          }state = STAT_INIT;



          int count = 0;

          const char* p = text;



          /*在一個循環中,指針從緩沖區頭移動緩沖區尾*/

          for(p = text; *p != '\0'; p++)

          {

          switch(state)

          {

          case STAT_INIT:

          {

          if(IS_WORD_CHAR(*p))

          {

          /*指針指向單詞字符,狀態轉換為單詞內*/

          state = STAT_IN_WORD;

          }

          else

          {

          /*指針指向非單詞字符,狀態轉換為單詞外*/

          state = STAT_OUT_WORD;

          }

          break;

          }

          case STAT_IN_WORD:

          {

          if(!IS_WORD_CHAR(*p))

          {

          /*指針指向非單詞字符,狀態轉換為單詞外,增加單詞計數*/

          count++;

          state = STAT_OUT_WORD;

          }

          break;

          }

          case STAT_OUT_WORD:

          {

          if(IS_WORD_CHAR(*p))

          {

          /*指針指向單詞字符,狀態轉換為單詞內*/

          state = STAT_IN_WORD;

          }

          break;

          }

          default:break;

          }

          }



          if(state == STAT_IN_WORD)

          {

          /*如果由單詞內進入接受狀態,增加單詞計數*/

          count++;

          }



          return count;

          }

          用狀態機來解這道題目,思路清晰,程序簡單,不易出錯。

          這道題目只是為了展示一些奇技淫巧,還是有一些實際用處呢?回答這個問題之前,我們先對上面的程序做點擴展,不只是統計單詞的個數,而且要分離出里面的每個單詞。

          int word_segmentation(const char* text, OnWordFunc on_word, void* ctx)

          {

          enum _State

          {

          STAT_INIT,

          STAT_IN_WORD,

          STAT_OUT_WORD,

          }state = STAT_INIT;



          int count = 0;

          char* copy_text = strdup(text);

          char* p = copy_text;

          char* word = copy_text;



          for(p = copy_text; *p != '\0'; p++)

          {

          switch(state)

          {

          case STAT_INIT:

          {

          if(IS_WORD_CHAR(*p))

          {

          word = p;

          state = STAT_IN_WORD;

          }

          break;

          }

          case STAT_IN_WORD:

          {

          if(!IS_WORD_CHAR(*p))

          {

          count++;

          *p = '\0';

          on_word(ctx, word);

          state = STAT_OUT_WORD;

          }

          break;

          }

          case STAT_OUT_WORD:

          {

          if(IS_WORD_CHAR(*p))

          {

          word = p;

          state = STAT_IN_WORD;

          }

          break;

          }

          default:break;

          }

          }



          if(state == STAT_IN_WORD)

          {

          count++;

          on_word(ctx, word);

          }



          free(copy_text);



          return count;

          }

          狀態機不變,只是在狀態轉換時,做是事情不一樣。這里從“單詞內”轉換到其它狀態時,增加單詞計數,并分離出當前的單詞。至于拿分離出的單詞來做什么,由傳入的回調函數決定,比如可以用來統計每個單詞出現的頻率。

          但如果討論還是限于英文文章,這個程序的意義仍然不大,現在來做進一步擴展。我們考慮的文本不再是英文文章,而是一些文本數據,這些數據由一些分隔符分開,我們把數據稱為token,現在我們要把這些token分離出來。

          typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);



          #define IS_DELIM(c) (strchr(delims, c) != NULL)

          int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)

          {

          enum _State

          {

          STAT_INIT,

          STAT_IN,

          STAT_OUT,

          }state = STAT_INIT;



          int count = 0;

          char* copy_text = strdup(text);

          char* p = copy_text;

          char* token = copy_text;



          for(p = copy_text; *p != '\0'; p++)

          {

          switch(state)

          {

          case STAT_INIT:

          case STAT_OUT:

          {

          if(!IS_DELIM(*p))

          {

          token = p;

          state = STAT_IN;

          }

          break;

          }

          case STAT_IN:

          {

          if(IS_DELIM(*p))

          {

          *p = '\0';

          on_token(ctx, count++, token);

          state = STAT_OUT;

          }

          break;

          }

          default:break;

          }

          }



          if(state == STAT_IN)

          {

          on_token(ctx, count++, token);

          }



          on_token(ctx, -1, NULL);

          free(copy_text);



          return count;

          }

          用分隔符分隔的文本數據有很多,如:

          環境PATH,它由‘:’分開的多個路徑組成。如:
          /usr/lib/qt-3.3/bin:/usr/kerberos/bin:/backup/tools/jdk1.5.0_18/bin/:/usr/lib/ccache:/usr/local/bin:/bin:/usr/bin:/home/lixianjing/bin

          文件名,它由‘/’分開的路徑組成。如:
          /usr/lib/qt-3.3/bin

          URL中的參數,它‘&’分開的多個key/value對組成。
          hl=zh-CN&q=limodev&btnG=Google+搜索&meta=&aq=f&oq=

          所有這些數據都可以用上面的函數處理,所以這個小函數是頗具實用價值的。

          posted on 2009-07-10 21:16 石頭@ 閱讀(325) 評論(0)  編輯  收藏 所屬分類: 基礎技術

          主站蜘蛛池模板: 濮阳市| 商丘市| 芷江| 南城县| 即墨市| 连云港市| 巴里| 班戈县| 珲春市| 习水县| 棋牌| 镇赉县| 长兴县| 温宿县| 鸡西市| 吉安县| 上栗县| 蚌埠市| 云和县| 北安市| 泽普县| 宁强县| 黄石市| 慈溪市| 焉耆| 青龙| 澜沧| 谷城县| 南汇区| 平武县| 巴彦淖尔市| 洞头县| 中江县| 绍兴县| 揭西县| 交口县| 望谟县| 华宁县| 涿鹿县| 多伦县| 射阳县|