11) 第二章 索引:文档优先策略(Boosting)

    请先确认一句话:“并非人人生而平等!”。对于Document和Field也是如此。

    假设你现在需要索引一些邮件。要求是,搜索结果中,船长发出的邮件要排在船员的前面!如何实现?

    还好Lucene为你提供了它的实现,而且非常简单:boosting. 每个文档都拥有一个优先权重因数,默认情况下它的值是1.0, 你可以通过改变此值来实现上面的要求。重要的文档(此例中为船长的邮件),我们可以让这个数大于1.0, 比如2.0如何?次要的文档(此例中为船员的邮件),我们可以让这个数小于1.0, 比如0.5。 当然,也可以让重要的为3.0,次要的为2.0,怎么设计随你。

    那么怎么改变这个因数呢? Lucene API提供了一个独立的方法:setBoost(float); 就是这么简单!

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

public class IndexBoostingTest extends TestCase{
	
	private Directory directory;
	
	protected void setUp() throws Exception {
		directory = new RAMDirectory();
		IndexWriter writer = getWriter(); 
		
		List<Email> mails = makeSomeEmails();
		for(Email e : mails){
			Document doc = new Document();
			doc.add(new Field("senderEmail", e.getSenderEmail(),
					Field.Store.YES,
					Field.Index.NOT_ANALYZED));
			doc.add(new Field("senderName", e.getSenderName(),
					Field.Store.YES,
					Field.Index.ANALYZED));
			doc.add(new Field("subject", e.getSubject(),
					Field.Store.YES,
					Field.Index.ANALYZED));
			doc.add(new Field("body", e.getBody(),
					Field.Store.NO,
					Field.Index.ANALYZED));
			
			// 关键代码:设置文档的优先权重因数
			if(Email.IMPORTANT.equals(e.getSenderDomain())){
				doc.setBoost(1.5F);
			}else if(Email.UNIMPORTANT.equals(e.getSenderDomain())){
				doc.setBoost(0.5F);
			}else{
				//此处写不写都一样,默认的优先权重因数是1.0
				doc.setBoost(1F);
			}
			writer.addDocument(doc);
		}
		writer.close();
	}
	
	//事实上不能这么写测试用例,因为这种测试很不严格。
    //排序的结果除了受boost影响还取决于文档与查询词的匹配度等
	public void testBoostResult() throws IOException {
		IndexSearcher is = new IndexSearcher(directory); 
		Query query = new TermQuery(new Term("body", "团")); 

        TopDocs topDocs = is.search(query, 3);
        ScoreDoc[] docs = topDocs.scoreDocs;
        
        //我们期望的排序结果是:Luffy、Sanji、Zoro
        String luffy = is.doc(docs[0].doc).get("senderName");
        String sanji = is.doc(docs[1].doc).get("senderName");
        String zoro = is.doc(docs[2].doc).get("senderName");
        
        assertEquals("Luffy", luffy);  //路飞排第一啦~~他的boost是1.5
        assertEquals("Sanji", sanji);  //香吉士采用了默认的boost是1.0
        assertEquals("Zoro", zoro);    //容易迷路的家伙boost是0.5
	}
	
	
	private IndexWriter getWriter() throws IOException { 
		return new IndexWriter(directory, new StandardAnalyzer(Version.LUCENE_30),
				IndexWriter.MaxFieldLength.UNLIMITED);
	}
	
	//模拟测试数据
	private List<Email> makeSomeEmails(){
		
		ArrayList<Email> testData = new ArrayList<Email>();
		
		//测试数据1 不设置boost
		Email mail1 = new Email();
		mail1.setSenderEmail("[email protected]");
		mail1.setSenderName("Sanji");
		mail1.setSenderDomain("普通的~~");
		mail1.setSubject("海贼");
		mail1.setBody("草帽海贼团厨师,金发,有着卷曲眉毛,永远遮住半边脸的家伙,其左眼是个迷,香烟不离口,海贼中的绅士");
		testData.add(mail1);
		
		//测试数据2 设置较高的boost
		Email mail2 = new Email();
		mail2.setSenderEmail("Monkey·D·[email protected]");
		mail2.setSenderName("Luffy");
		mail2.setSenderDomain(Email.IMPORTANT);
		mail2.setSubject("海贼");
		mail2.setBody("草帽海贼团船长,特征是头戴草帽,顽强,坚定,喜欢探险,最爱吃肉");
		testData.add(mail2);
		
		//测试数据3 设置较低的boost(喜欢索隆的别喷我,我也喜欢...举个例子而已)
		Email mail3 = new Email();
		mail3.setSenderEmail("[email protected]");
		mail3.setSenderName("Zoro");
		mail3.setSenderDomain(Email.UNIMPORTANT);
		mail3.setSubject("海贼");
		mail3.setBody("草帽海贼团剑士,绿色头发,左耳戴三只黄色露珠耳环");
		testData.add(mail3);
		
		return testData;
	}
	
}

class Email{
	
	public static String IMPORTANT = "important";
	public static String UNIMPORTANT = "unimportant";
	
	private String senderEmail;
	private String senderName;
	private String senderDomain;
	private String subject;
	private String body;
	
	public String getSenderEmail() {
		return senderEmail;
	}
	public void setSenderEmail(String senderEmail) {
		this.senderEmail = senderEmail;
	}
	public String getSenderName() {
		return senderName;
	}
	public void setSenderName(String senderName) {
		this.senderName = senderName;
	}
	public String getSenderDomain() {
		return senderDomain;
	}
	public void setSenderDomain(String senderDomain) {
		this.senderDomain = senderDomain;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getBody() {
		return body;
	}
	public void setBody(String body) {
		this.body = body;
	}
	
}

     问题又来了,如果我们认为邮件的标题中出现的关键词比正文中更重要呢?也就是说要使名为"subject"的Field优先于"body"的。

    哈哈,其实也很简单,只要针对这个域调用 setBoost(float); 即可:

Field subjectField = new Field("subject", subject,
                     Field.Store.YES,
                     Field.Index.ANALYZED);
subjectField.setBoost(1.2F);

    事实上,之前的例子中对document设置boost值,相当于对该document下的所有Field设置了相同的boost值。理所当然,你也可以为单个Field设置boost值!

    必须要指出,如果需要更改已经设置的boost值,那么只能重新索引整个document,然后为它设置另一个boost值。这看起来很糟糕,毕竟客户的需求总是在变化的。放心,我们还可以通过在搜索阶段自定义排序来实现此效果,那将更具动态性,更加灵活。现在,你可以为那些永远要排在前面的文档设置一个无比大的boost值了!


猜你喜欢

转载自bun-ny.iteye.com/blog/1074245