经常有一种情景是这样的:我们索引了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. C语言基础一

    C语言学习 C语言的特点 语言简单.紧凑.使用方便.灵活 运算符丰富 数据类型丰富 具有结构化的控制语句 语法限制不太严格,程序设计自由度大 C语言允许直接访问物理地址,可以对硬件进行直接操作 生成代 ...

  2. java学习笔记 --- IO(3)

    1.FileReader:读取字符流,默认GBK public class CharStreamDemo { public static void main(String[] args) throws ...

  3. HTTP协议 与 Requests库

    HTTP协议 与 Requests库: 1  HTTP协议: 2 URL作为网络定位的标识: >>>> 用户通过url来定位资源 >>>> 然后通过 g ...

  4. dirent.h

    #include <dirent.h> 是POSIX.1标准定义的unix类目录操作的头文件,包含了许多UNIX系统服务的函数原型,例如opendir函数.readdir函数. opend ...

  5. 已知一个数组a[N]来构造数组b[N]的有趣算法题

    给定一个数组a[N],我们希望构造数组b[N],其中b[i]=a[0]*a[1]*...*a[N-1]/a[i].在构造过程要求满足:1.不使用除法:2.O(1)空间复杂度和O(n)时间复杂度:3.除 ...

  6. HihoCoder 1190连通性·四

    连通性·四 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho从约翰家回到学校时,网络所的老师又找到了小Hi和小Ho. 老师告诉小Hi和小Ho:之前的分组出了 ...

  7. [Luogu3674]小清新人渣的本愿

    luogu 题意 给你一个序列a,长度为n,有m次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ...

  8. 1625 codevs数字金字塔

    1625 数字金字塔 USACO  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解   题目描述 Description 考虑在下面被显示的数字金字塔. 写 ...

  9. MyBatis嵌套查询column传多个参数描述

    代码如下,红色部分为关键代码. 注意parameterType要为java.util.HashMap <resultMap id="baseResultMap" type=& ...

  10. [转]你知道用AngularJs怎么定义指令吗?--很详细

    前言 最近学习了下angularjs指令的相关知识,也参考了前人的一些文章,在此总结下. 欢迎批评指出错误的地方.   Angularjs指令定义的API AngularJs的指令定义大致如下 ang ...