«
用PHP简易实现中文分词

时间:2008-5-31    作者:Deri    分类: 分享


   <p>  PHP去做中文分词并不是一个太明智的举动, :p</p><p>  下面是我根据网上找的一个字典档, 简易实现的一个分词程序.</p><p>  (注: 字典档是gdbm格式, key是词 value是词频, 约4万个常用词)</p><code><?php<br />//中文分词系统简易实现办法<br />//切句单位:凡是ascii值<128的字符<br />//常见双字节符号:《》,。、?“”;:!¥…… %$#@^&*()[]{}|\/"'<br />//可以考虑加入超常见中文字: 的 和 是 不 了 啊 (不过有特殊字比如 "打的" "郑和" .. :p)<br />//计算时间<br />function getmicrotime(){<br />  list($usec, $sec) = explode(" ",microtime());<br />  return ((float)$usec + (float)$sec);<br />}<br />$time_start = getmicrotime();<br />//词典类<br />class ch_dictionary {<br />  var $_id;<br />  function ch_dictionary($fname = "") {<br />    if ($fname != "") {<br />      $this->load($fname);<br />    }<br />  }<br />  // 根据文件名载入字典 (gdbm数据档案)<br />  function load($fname) {<br />    $this->_id = dba_popen($fname, "r", "gdbm");<br />    if (!$this->_id) {<br />      echo "failed to open the dictionary.($fname)<br><br />";<br />      exit;<br />    }<br />  }<br />  // 根据词语返回频率, 不存在返回-1<br />  function find($word) {<br />    $freq = dba_fetch($word, $this->_id);<br />    if (is_bool($freq)) $freq = -1;<br />    return $freq;<br />  }<br />}<br />// 分词类: (逆向)<br />// 先将输入的字串正向切成句子, 然后一句一句的分词, 返回由词组成的数组.<br />class ch_word_split {<br />  var $_mb_mark_list;  // 常见切分句子的全角标点<br />  var $_word_maxlen;  // 单个词最大可能长度(汉字字数)<br />  var $_dic;    // 词典...<br />  var $_ignore_mark;  // true or false<br />  <br />  function ch_word_split () {<br />    $this->_mb_mark_list = array(","," ","。","!","?",":","……","、","“","”","《","》","(",")");<br />    $this->_word_maxlen = 12;  // 12个汉字<br />    $this->_dic = NULL;<br />    $this->_ignore_mark = true;<br />  }<br />  // 设定字典<br />  function set_dic($fname) {<br />    $this->_dic = new ch_dictionary($fname);<br />  }<br />  function set_ignore_mark($set) {<br />    if (is_bool($set)) $this->_ignore_mark = $set;<br />  }<br />  // 将字串切成句子再加以切分成词<br />  function string_split($str, $func = "") {    <br />    $ret = array();<br />    <br />    if ($func == "" || !function_exists($func)) $func = "";    <br />    <br />    $len = strlen($str);<br />    $qtr = "";<br />    for ($i = 0; $i < $len; $i++) {<br />      $char = $str[$i];<br />      if (ord($char) < 0xa1) {<br />        // 读取到一个半角字符<br />        if (!empty($qtr)) {<br />          $tmp = $this->_sen_split($qtr);<br />          $qtr = "";<br />          if ($func != "") call_user_func($func, $tmp);          <br />          else $ret = array_merge($ret, $tmp);          <br />        }<br />        // 如果是单词或数字. 根据 char 将数据读取到 >= 0xa1为止<br />        if ($this->_is_alnum($char)) {<br />          do {<br />            if (($i+1) >= $len) break;<br />            $char2 = substr($str, $i + 1, 1);<br />            if (!$this->_is_alnum($char2)) break;<br />            $char .= $char2;<br />            $i++;<br />          } while (1);<br />          if ($func != "") call_user_func($func, array($char));<br />          else $ret[] = $char;          <br />        }<br />        elseif ($char == ' ' || $char == "  ") {<br />          // nothing.<br />          continue;<br />        }<br />        elseif (!$this->_ignore_mark) {<br />          if ($func != "") call_user_func($func, array($char));<br />          else $ret[] = $char;          <br />        }<br />      }<br />      else {<br />        // 双字节字符.<br />        $i++;<br />        $char .= $str[$i];<br />        <br />        if (in_array($char, $this->_mb_mark_list)) {<br />          if (!empty($qtr)) {<br />            $tmp = $this->_sen_split($qtr);<br />            $qtr = "";<br />            if ($func != "") call_user_func($func, $tmp);<br />            else $ret = array_merge($ret, $tmp);<br />          }<br />          if (!$this->_ignore_mark) {<br />            if ($func != "") call_user_func($func, array($char));<br />            else $ret[] = $char;<br />          }<br />        }<br />        else {<br />          $qtr .= $char;<br />        }<br />      }<br />    }<br />    <br />    if (strlen($qtr) > 0) {<br />      $tmp = $this->_sen_split($qtr);<br />      if ($func != "") call_user_func($func, $tmp);      <br />      else $ret = array_merge($ret, $tmp);      <br />    }<br />    // return value<br />    if ($func == "") {<br />      return $ret;<br />    }<br />    else {<br />      return true;<br />    }<br />  }<br />  // 将句子切成词, 逆向<br />  function _sen_split($sen) {<br />    $len = strlen($sen) / 2;<br />    $ret = array();<br />    for ($i = $len - 1; $i >= 0; $i--) {<br />      // 如: 这是一个分词程序<br />      <br />      // 先取得最后一个字<br />      $w = substr($sen, $i * 2, 2);<br />      // 最终的词长<br />      $wlen = 1;<br />      <br />      // 开始逆向匹配到最大长度.<br />      $lf = 0; // last freq<br />      for ($j = 1; $j <= $this->_word_maxlen; $j++) {<br />        $o = $i - $j;<br />        if ($o < 0) break;<br />        $w2 = substr($sen, $o * 2, ($j + 1) * 2);<br />        <br />        $tmp_f = $this->_dic->find($w2);<br />        //echo "{$i}.{$j}: $w2 (f: $tmp_f)<br />";<br />        if ($tmp_f > $lf) {<br />          $lf = $tmp_f;<br />          $wlen = $j + 1;<br />          $w = $w2;<br />        }<br />      }<br />      // 根据 $wlen 将 $i 偏移了<br />      $i = $i - $wlen + 1;<br />      array_push($ret, $w);<br />    }<br />    $ret = array_reverse($ret);<br />    return $ret;<br />  }<br />  // 判断字符是不是 字母数字_- [0-9a-z_-]<br />  function _is_alnum($char) {<br />    $ord = ord($char);<br />    if ($ord == 45 || $ord == 95 || ($ord >= 48 && $ord <= 57))<br />      return true;<br />    if (($ord >= 97 && $ord <= 122) || ($ord >= 65 && $ord <= 90))<br />      return true;<br />    return false;<br />  }<br />}<br />// 分词后的回调函数<br />function call_back($ar) {  <br />  foreach ($ar as $tmp) {<br />    echo $tmp . " ";<br />    //flush();<br />  }<br />}<br />// 实例(如果没有输入就从 sample.txt中读取):<br />$wp = new ch_word_split();<br />$wp->set_dic("dic.db");<br />if (!isset($_REQUEST['testdat']) || empty($_REQUEST['testdat'])) {<br />  $data = file_get_contents("sample.txt");<br />}<br />else {<br />  $data = & $_REQUEST['testdat'];<br />}<br />// output<br />echo "<h3>简易分词演示</h3><br />";<br />echo "<hr><br />";<br />echo "分词结果(" . strlen($data) . " chars): <br><br /><textarea cols=100 rows=10><br />";<br />// 设定是否忽略不返回分词符号(标点,常用字)<br />$wp->set_ignore_mark(false);<br />// 执行切分, 如果没有设置 callback 函数, 则返回由词组成的array<br />$wp->string_split($data, "call_back");<br />$time_end = getmicrotime();<br />$time = $time_end - $time_start;<br />echo "</textarea><br><br />本次分词耗时: $time seconds <br><br />";<br />?><br /><hr><br /><form method=post><br />您也可以在下面文本框中输入文字,提交后试验分词效果:<br><br /><textarea name=testdat cols=100 rows=10></textarea><br><br /><input type=submit><br /></form><br /><hr></code></p>