经常有一种情景是这样的:我们索引了N年的文章,而查询时候无论直接用相关度、或者用时间排序,都是比较鲁莽的;我们想要一种既要相关度比较高,又要时间上比较新的文章。

这时候的解决办法就是,自定义日期衰减的ValueSourceQuery,然后在正常normalQuery的基础上后遭CustomScoreQuery即可。



下面给出2种在solr中使用日期衰减的方法

比如我们的索引中的时间字段是time,正常查询是title:哈哈 keyword:哈哈,

1、使用已有的各种functionQuery的组合

solr中日期衰减的查询方式则是:{!boost b=recip(ms(NOW/HOUR,time),3.16e-11,1,1)}title:哈哈 keyword:哈哈

前面这个式子的含义可以去查询solr wiki:http://wiki.apache.org/solr/FunctionQuery#What_is_a_Function.3F



这个方式,时间的衰减比较平缓,比如昨天的权重是0.999,前天是0.998,一年前的今天是0.5.。。。。。

如果我们需要一个时间衰减比较剧烈的方式,则需要自定义了。

2、自定义ValueSource:实现FieldCacheSource

这里我们以lucene4.1为例(各个版本的代码有所偏差,需要根据情况实现),大致原理是:给每个时间设置一个时间衰减因子,然后把文档的相关度乘上时间因子就是最后得分。

2.1和2.3中的实现方式,在得到相关度以后,每次搜索,都会获取所有文档的时间字段,并计算时间权重值。这在效率上是比较慢的,数据在千万级别的时候还可接受,更多的数据则会比较慢。

所以第3部分提供了这个思路的另一个实现方式,它只会计算搜索结果中的文档的时间权重,大大降低了时间。

2.1 先实现是 一个ValueSource。


import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.valuesource.FieldCacheSource;
public class DateFunction extends FieldCacheSource {
private static final long serialVersionUID = 6752223682280098130L;
private static long now;
public DateFunction(String field) {
super(field);
now = System.currentTimeMillis();
}
@Override
public FunctionValues getValues(Map context,
AtomicReaderContext readerContext) throws IOException {
long[] times = cache.getLongs(readerContext.reader(), field, false);//获取各个记录中的时间字段毫秒数
final float[] weights = new float[times.length];
for (int i = 0; i < times.length; i++) {
weights[i] = ScoreUtils.getNewsScoreFactor(now, times[i]);//获取每个记录的时间衰减因子
}
return new FunctionValues() {//返回
@Override
public float floatVal(int doc) {
return weights[doc];
}
@Override
public int intVal(int doc) {
return (int) weights[doc];
}
@Override
public String toString(int doc) {
return description() + '=' + intVal(doc);
}
};
}
}

其中用到的scoreutils定义如下:


public class ScoreUtils {
private static float[] daysDampingFactor = new float[32];
private static float demoteboost = 0.5f;
static {
daysDampingFactor[0] = 1;
for (int i = 1; i < 7; i++) {
daysDampingFactor[i] = daysDampingFactor[i - 1] * demoteboost;
}
for (int i = 7; i < 31; i++) {
daysDampingFactor[i] = daysDampingFactor[i / 7 * 7 - 1]
* demoteboost;
}
for (int i = 31; i < daysDampingFactor.length; i++) {
daysDampingFactor[i] = daysDampingFactor[i / 31 * 31 - 1]
* demoteboost;
}
}
private static float dayDamping(int delta) {
return delta < daysDampingFactor.length ? daysDampingFactor[delta]
: daysDampingFactor[daysDampingFactor.length - 1];
}
public static float getNewsScoreFactor(long now, long time) {
float factor = 1;
int day = (int) (time / MiscConstants.DAY_MILLIS);
int nowDay = (int) (now / MiscConstants.DAY_MILLIS);
if (day < nowDay) {
factor = dayDamping(nowDay - day);
} else if (day > nowDay) {
factor = Float.MIN_VALUE;
} else if (now - time <= MiscConstants.HALF_HOUR_MILLIS && now >= time) {
factor = 2;
}
return factor;
}
public static float getNewsScoreFactor(long time) {
long now = System.currentTimeMillis();
return getNewsScoreFactor(now, time);
}
}
class MiscConstants {
/** 24x60x60x1000 */
public static final long DAY_MILLIS = 86400000;
/** 24x60x60x1000 */
public static final long DAY_SECONDS = 86400;
/** 60x1000 */
public static final int MINUTE_MILLIS = 60000;
/** 60x1000 */
public static final int HALF_HOUR_MILLIS = 1800000;
/** 60x1000 */
public static final int MINUTE_SECONDS = 60;
}

2.2 如果是在lucene中使用,则在正常的normalQuery基础上,包装一下即可,如下:

ValueSourceQuery dateBooster = new ValueSourceQuery(new DateFieldSource("ptime")); 

CustomScoreQuery dateScoreQuery = new CustomScoreQuery(normalQuery, dateBooster);

2.3 如果是在solr中使用个,还需要实现valuesourcepaser


import org.apache.lucene.queries.function.ValueSource;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.search.FunctionQParser;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.search.ValueSourceParser;
public class DateSourceParser extends ValueSourceParser {
@Override
public void init(NamedList namedList) {
}
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new DateFunction("ptime");// 被自定义排序的字段
}
}

并且要在solrconfig.xml的config标签中定义这个parser

<valueSourceParser name="dateDeboost" class="org.netease.solr.custom.DateSourceParser" />

这样在搜索的时候就可使用了{!boost b=dateDeboost()}title:哈哈 keyword:哈哈

ps:这里还支持参数;不用参数的时候dateDeboost(),这样调用就可以了。使用参数的时候dateDeboost(param),fqp.parseArg()可以获取参数。这样就可更自由的控制一下逻辑。



3、自定义ValueSource:重用ValueSource

阅读solr的代码后,发现solr中的function query的实现更优雅。

这里记录了solr自定义的各种函数的定义org.apache.solr.search.ValueSourceParser。

其实思路就是不再逐个记录的遍历,主要区别是getValues方法中的实现。具体实现如下:

3.1 实现一个valuesource


import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.FloatDocValues;
import org.apache.lucene.search.IndexSearcher;
public class DateFunction extends ValueSource {
protected final ValueSource source;
public DateFunction(ValueSource source) {
this.source = source;
}
@Override
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
final FunctionValues vals = source.getValues(context, readerContext);
return new FloatDocValues(this) {
@Override
public float floatVal(int doc) {
long ptime = vals.longVal(doc);
return ScoreUtils.getNewsScoreFactor(ptime);
}
};
}
@Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
source.createWeight(context, searcher);
}
@Override
public String description() {
return "This is org.sling.DateFunction.";
}
@Override
public int hashCode() {
return source.hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DateFunction))
return false;
DateFunction other = (DateFunction) o;
return source.equals(other.source);
}
}

其中scoreutils的定义还是和上面一样。

3.2 在solr中使用

import org.apache.lucene.queries.function.ValueSource;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.search.FunctionQParser;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.search.ValueSourceParser;
public class DateSourceParser extends ValueSourceParser {
    @Override
    public void init(NamedList namedList) {
    }
    @Override
    public ValueSource parse(FunctionQParser fp) throws SyntaxError {
        //ValueSource不能获取两次。所以fp.parseValueSourceList()和fp.parseValueSource()只能用一个
        ValueSource source = fp.parseValueSource();//获取这个ValueSource,并在一个sercher中重用它
        return new DateFunction(source);
    }
}

3.3在lucene中使用

读一下fp.parseValueSource()这部分代码,可以发现,其实这也是用了lucene中的一些类。下面直接给出实现吧


ValueSource valueSource = new LongFieldSource(timeField);
FunctionQuery scoreField = new FunctionQuery(new DateFunction(valueSource));
CustomScoreQuery dateScoreQuery = new CustomScoreQuery(query, scoreField);
// TopDocs top = indexSearcher.search(query, 5);//普通查询
TopDocs top = indexSearcher.search(dateScoreQuery, 5);//日期衰减查询
ScoreDoc[] scoreDocs = top.scoreDocs;

可以发现,在lucene中普通查询和日期衰减查询的区别就是:构造的查询条件不一样而已。。。

lucene、solr中的日期衰减方法-------function query --尚未测试在solr4.8的更多相关文章

  1. Solr中的日期/时间表示

    摘要: Solr的日期字段(TrieDateField 和DateRangeField)可以对一个时间点以毫秒精度表示. 格式 Solr中的日期有很严格的格式限制: YYYY-MM-DDThh:mm: ...

  2. Oracle中的日期处理方法

    日期处理方法                                                        当前日期和时间 Select sysdate from dual; 本月最后 ...

  3. 指尖上的电商---(8)Solr中Facet的使用方法

    在大型电子商务站点中,在商品列表页,我们都能够看到商品按分类,品牌,价格的分类显示,例如以下图,这些我们能够使用solr中的facet功能实现. facet的基本功能就是对搜索结果中的商品进行分类. ...

  4. AS2在FLASH中调用EXE文件方法详细说明 已测试可行

    熟悉FLASH功能的朋友都知道fscommand在FLASH中是一个经常用来控制窗口全屏或退出的命令,同时它也是FLASH调用外部可执行程序的一种方法,使用fscommand命令格式如下: fscom ...

  5. JavaScript 中的日期和时间

    前言 本篇的介绍涵盖以下部分: 1. 时间标准指的是什么?UCT和GMT 的概念.关联和区别? 2. 时间表示标准有哪些? 3. JS 中时间的处理 日期时间标准 日期的标准就不多说了 -- 公元纪年 ...

  6. 【转】Java8中list转map方法总结

    https://blog.csdn.net/zlj1217/article/details/81611834 背景在最近的工作开发之中,慢慢习惯了很多Java8中的Stream的用法,很方便而且也可以 ...

  7. PHP 中 16 个魔术方法详解

    PHP 中 16 个魔术方法详解   前言 PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods),这些方法在PHP中充当了举足轻重的作用. 魔术方法包括: __constru ...

  8. 在Lucene或Solr中实现高亮的策略

    一:功能背景 近期要做个高亮的搜索需求,曾经也搞过.所以没啥难度.仅仅只是原来用的是Lucene,如今要换成Solr而已,在Lucene4.x的时候,散仙在曾经的文章中也分析过怎样在搜索的时候实现高亮 ...

  9. 在java中进行日期时间比较的4种方法

    1. Date.compareTo() java.util.Date提供了在Java中比较两个日期的经典方法compareTo(). 如果两个日期相等,则返回值为0. 如果Date在date参数之后, ...

随机推荐

  1. asp.net JS取值

    <script> $(function () { $.post("RegisterNew.aspx", { "ddlprovince": $(&qu ...

  2. hdu3605最大流

    题意:给n个人m个星球,每个人适不适合去某个星球,每个星球的最大容量,看能不能满足所有人能去一个星球 题解:可以看出如果直接建边,边的个数是1e5左右的,这样时间肯定会爆掉,那么我们考虑用二进制状态压 ...

  3. c、rust、golang、swift性能比较

    mac 计算速度视觉判断是(由好到差):c > rust > swift > golang 内存开销在mac上是(由好到差):c > rust > golang > ...

  4. Unity3D重要知识点(转)

    Unity3D重要知识点 数据结构和算法很重要!图形学也很重要!大的游戏公司很看重个人基础,综合能力小公司看你实际工作能力,看你的Demo. 1.什么是渲染管道? 是指在显示器上为了显示出图像而经过的 ...

  5. iOS 检查指定日期是否在当前日期之前

    iOS检查指定日期是否在当前日期之前, 直接上代码: - (BOOL)checkProductDate: (NSString *)tempDate { NSDateFormatter *dateFor ...

  6. EF ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象

    今天编码过程中遇到这个问题,用EF 更新数据库,将组织好的数据传递到ef的上下文中,本以为附加上去更新,一切就ok了,不过事实证明没这么顺利 ----------------------------- ...

  7. 2017.11.28 Enginering management:problem-solving ability

    Today,my colleague is on bussiness trip. going to customer factory in jiangxi. slove the color diffe ...

  8. 通过 objc_setAssociatedObject alert 和 button关联 及传值

    原文地址 http://blog.csdn.net/lengshengren/article/details/16886915 //唯一静态变量key static const char associ ...

  9. P1605 迷宫(洛谷)

    题目背景 迷宫 [问题描述] 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在迷宫中移动有上下左右 ...

  10. Linux终端录屏与播放 script 命令

    本文由Suzzz原创,发布于 http://www.cnblogs.com/Suzzz/p/4107700.html ,转载请保留此声明. 有时候可能想要记录在终端的所有操作包括输出等,将来作为视频播 ...