similar_text 是PHP内置的字符串类似度对比函数,是使用方式最便捷的一种,可是由于它的时间复杂度是 O(N**3),处理时间会随着内容长度增长,若比较5000字以上的文章,或者比较文章的量级比较大不建议使用,只是单篇文章对单篇文章能够使用。php
解决方案是首先进行文章分词能够用结巴或者迅搜分词服务进行文章分词,而后将须要对比的文章分词结果存入redis,在有新文章进行对比的时候从redis将全部文章的分词结果从内存中取出来而后进行类似度对比,逐词进行类似度计算。类似度计算的准确性很高,可是对比的文章量很是大的时候,处理时间仍是会很长,5000文章的类似度计算须要近30Smysql
主要计算代码:redis
Class TextSimilarity { /** * [排除的词语] * * @var array */ private $_excludeArr = array('的', '了', '和', '呢', '啊', '哦', '恩', '嗯', '吧'); /** * [词语分布数组] * * @var array */ private $_words = array(); /** * [分词后的数组一] * * @var array */ private $_segList1 = array(); /** * [分词后的数组二] * * @var array */ private $_segList2 = array(); private static $test1 = array(); private static $test2 = array(); /** * [分词两段文字] * * @param [type] $text1 [description] * @param [type] $text2 [description] */ public function __construct($text1, $text2) { $this->_segList1 = is_array( $text1 ) ? $text1 : $this->segment( $text1 ); $this->_segList2 = is_array( $text2 ) ? $text2 : $this->segment( $text2 ); } /** * [外部调用] * * @return [type] [description] */ public function run() { $this->analyse(); $rate = $this->handle(); return $rate ? $rate : 'errors'; } /** * [分析两段文字] */ private function analyse() { //t1 foreach ($this->_segList1 as $v) { if (!in_array($v, $this->_excludeArr)) { if (!array_key_exists($v, $this->_words)) { $this->_words[$v] = array(1, 0); } else { $this->_words[$v][0] += 1; } } } //t2 foreach ($this->_segList2 as $v) { if (!in_array($v, $this->_excludeArr)) { if (!array_key_exists($v, $this->_words)) { $this->_words[$v] = array(0, 1); } else { $this->_words[$v][1] += 1; } } } } /** * [处理类似度] * * @return [type] [description] */ private function handle() { $sum = $sumT1 = $sumT2 = 0; foreach ($this->_words as $word) { $sum += $word[0] * $word[1]; $sumT1 += pow($word[0], 2); $sumT2 += pow($word[1], 2); } $rate = $sum / (sqrt($sumT1 * $sumT2)); return $rate; } /** * [分词 【http://www.xunsearch.com/scws/docs.php#pscws23】] * * @param [type] $text [description] * * @return [type] [description] * * @description 分词只是一个简单的例子,你能够使用任意的分词服务 */ private function segment( $text ) { $outText = array(); $xs = new XS('demo'); // 必须先建立一个 xs 实例,不然会抛出异常 $tokenizer = new XSTokenizerScws; // 直接建立实例 $tokenizer->setIgnore(); //处理 $outText = $tokenizer->setMulti(1)->getResult($text); $outText = array_column( $outText, 'word'); $res = $xs->getScwsServer(); $res->close(); return $outText; } }
SimHash的原理是将很长的一段文字降维成一个0和1组成的字符串,而后计算两个01字符串的类似度,从而算出两篇文章的类似程度。也是将文章先分词,计算存量文章的类似度存入redis或者mysql,须要的时候取出来对比,对比速度20000篇文章的计算时间基本上在2s之内,可是当文章字数很是小而且重复词很是多的时候会出现文章不相同可是类似度很是高的问题。
主要计算代码:sql
class SimHash { protected static $length = 256; protected static $search = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'); protected static $replace = array('0000','0001','0010','0011','0100','0101','0110','0111','1000','1001','1010','1011','1100','1101','1110','1111'); /** * [排除的词语] * * @var array */ private static $_excludeArr = array('的', '了', '和', '呢', '啊', '哦', '恩', '嗯', '吧','你','我',' '); public static function get(array &$set) { $boxes = array_fill(0, self::$length, 0); if (is_int(key($set))) $dict = array_count_values($set); else $dict = &$set; foreach ($dict as $element => $weight) { if ( in_array($element, self::$_excludeArr )){ continue; } $hash = hash('sha256', $element); $hash = str_replace(self::$search, self::$replace, $hash); $hash = substr($hash, 0, self::$length); $hash = str_pad($hash, self::$length, '0', STR_PAD_LEFT); for ( $i=0; $i < self::$length; $i++ ) { $boxes[$i] += ($hash[$i] == '1') ? $weight : -$weight; } } $s = ''; foreach ($boxes as $box) { if ($box > 0) $s .= '1'; else $s .= '0'; } return $s; } public static function hd($h1, $h2) { $dist = 0; for ($i=0;$i<self::$length;$i++) { if ( $h1[$i] != $h2[$i] ) $dist++; } return (self::$length - $dist) / self::$length; }