我使用Zend_Search_Lucene创建了一个简单的索引来搜索公司名称列表,因为我希望能够提供比简单的MySQL'LIKE%query%'更智能的搜索。我使用下面的代码,其中'companyname'是公司名称,'document_id'是每个文档的唯一ID(我知道Lucene在内部分配一个,但我知道可以更改,而我的文档ID将是静态的。
$index = Zend_Search_Lucene::create('test-index');
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::UnIndexed('document_id', 1));
$document->addField(Zend_Search_Lucene_Field::Text('companyname', 'XYZ Holdings'));
$index->addDocument($document);
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::UnIndexed('document_id', 2));
$document->addField(Zend_Search_Lucene_Field::Text('companyname', 'X.Y.Z. (Holdings) Ltd'));
$index->addDocument($document);
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::UnIndexed('document_id', 3));
$document->addField(Zend_Search_Lucene_Field::Text('companyname', 'X Y Z Ltd'));
$index->addDocument($document);
$index->commit();
但是,当我运行以下代码以查找名称中包含“XYZ”变体的所有公司时:
$index = Zend_Search_Lucene::open('test-index');
$hits = $index->find('companyname:XYZ');
foreach ($hits as $hit)
{
print "ID: " . $hit->document_id . "\n";
print "Score: " . $hit->score . "\n";
print "Company: " . $hit->companyname . "\n";
}
我最终得到以下结论:
ID: 1
Score: 1
Company: XYZ Holdings
我期待XYZ匹配所有文档,因为进行此搜索的目的是选择具有相同名称但标点符号略有不同的公司,这些公司无法在简单的LIKE子句中进行处理。有没有理由说Lucene与所有文件都不匹配,我能做些什么来解决这个问题?
如果我搜索'companyname:'x.y.z持有“',我会遇到同样的问题 - 除了'companyname:'x.y.z holdings''之外什么都不匹配。我希望Lucene能够确定“持有”和“持有”足够接近被视为匹配。
我很确定所有文档都已编入索引,因为如果我搜索“X.Y.Z”,我会获得文档2和3的匹配。
编辑:忘记提及PHP版本(带Suhosin-Patch的5.3.5-1ubuntu7.4)和Zend Framework版本(1.11.10-0ubuntu1)。
答案 0 :(得分:1)
您可以在索引内容之前预处理内容来解决问题。 Lucene将使用令牌,您需要将它们视为单独的单位。我在过去做了类似的事情以匹配版本号,因此搜索2.0也会提供2.0.3,但不是1.2.0。
这里的toCanonical()函数并不完美。我建议你自己编写并构建一个测试套件,以确保它按预期转换文本。它的作用是通过将看起来像首字母缩略词的东西分组来构建一个更长的字符串。您也可以在搜索查询中调用它。
您需要使用companyname_canonical而不是companyname进行搜索。
在Zend Lucene中,可能有更简洁的方法将其作为过滤器。您可能还想使用词干分析器来处理复数形式等。已经编写了porter stemmer的实现。 http://codefury.net/2008/06/a-stemming-analyzer-for-zends-php-lucene/
function toCanonical($text)
{
$out = $text . ' ';
$step = $text;
$pattern = '/([A-Z])[\s\.-]([A-Z])([^a-z])/';
while (preg_match($pattern, $step)) {
$step = preg_replace($pattern, '$1$2$3', $step);
$out .= $step . ' ';
}
return $out;
}
function createDocument($id, $companyName)
{
$canonicalName = toCanonical($companyName);
$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::UnIndexed('document_id', $id));
$document->addField(Zend_Search_Lucene_Field::Text('companyname', $companyName));
$document->addField(Zend_Search_Lucene_Field::UnStored('companyname_canonical', $canonicalName));
}
$index->addDocument(createDocument(1, 'XYZ Holdings'));
$index->addDocument(createDocument(1, 'X.Y.Z. (Holding) Company'));
答案 1 :(得分:0)
当你索引“XYZ Holdings”(比如你使用的是standardAnalyzer)时,会有两个标记“xyz”和“holdings”
对于“X.Y.Z.(Holdings)Ltd”&会有“x”,“y”,“z”,“holdings”和“ltd”
如果是“X Y Z Ltd”,则代币将为“x”,“y”,“z”和“ltd”
当您发出公司名称:“X.Y.Z”或公司名称:“X Y Z”时,案例2和案例3都匹配。 lucene无法知道案例1中的XYZ也是首字母缩略词。
我认为您应该编写自己的标记生成器来为“XYZ”,“X.Y.Z”和“X Y Z”生成相同的标记,但这可能会干扰不是首字母缩略词的其他大写单词