Skip to content

Commit

Permalink
Added support for random_score function:
Browse files Browse the repository at this point in the history
 * can be used to return matching results in random order

 Closes #1170
  • Loading branch information
uboness committed Aug 17, 2013
1 parent 610f262 commit c93eae8
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 4 deletions.
@@ -0,0 +1,99 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.common.lucene.search.function;

import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.Explanation;

/**
*
*/
public class RandomScoreFunction implements ScoreFunction {

private final PRNG prng;
private int docBase;

public RandomScoreFunction(long seed) {
this.prng = new PRNG(seed);
}

@Override
public void setNextReader(AtomicReaderContext context) {
this.docBase = context.docBase;
}

@Override
public double score(int docId, float subQueryScore) {
return prng.random(docBase + docId);
}

@Override
public double factor(int docId) {
return prng.seed;
}

@Override
public Explanation explainScore(int docId, Explanation subQueryExpl) {
Explanation exp = new Explanation();
exp.setDescription("random score function (seed: " + prng.originalSeed + ")");
exp.addDetail(subQueryExpl);
return exp;
}

@Override
public Explanation explainFactor(int docId) {
Explanation exp = new Explanation();
exp.setDescription("seed: " + prng.originalSeed + ")");
return exp;
}

/**
* Algorithm based on {@link java.util.Random} except this one is not
* thread safe
*/
static class PRNG {

private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;

final long originalSeed;
long seed;

PRNG(long seed) {
this.originalSeed = seed;
this.seed = (seed ^ multiplier) & mask;
}

public float random(int doc) {
if (doc == 0) {
doc = -17;
}
return nextFloat() * (doc ^ 0xCAFEBAB);
}

public float nextFloat() {
seed = (seed * multiplier + addend) & mask;
int r = (int)(seed >>> 24);
return r / ((float)(1 << 24));
}

}
}
Expand Up @@ -545,7 +545,6 @@ public static CustomFiltersScoreQueryBuilder customFiltersScoreQuery(QueryBuilde
* A query that allows to define a custom scoring function.
*
* @param queryBuilder The query to custom score
* @param scoreFunctionBuilder The score function used to re-score the query
*/
public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBuilder) {
return new FunctionScoreQueryBuilder(queryBuilder);
Expand All @@ -555,7 +554,6 @@ public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBui
* A query that allows to define a custom scoring function.
*
* @param filterBuilder The query to custom score
* @param scoreFunctionBuilder The score function used to re-score the query
*/
public static FunctionScoreQueryBuilder functionScoreQuery(FilterBuilder filterBuilder) {
return new FunctionScoreQueryBuilder(filterBuilder);
Expand Down
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.index.query.functionscore.factor.FactorParser;
import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionParser;
import org.elasticsearch.index.query.functionscore.lin.LinearDecayFunctionParser;
import org.elasticsearch.index.query.functionscore.random.RandomScoreFunctionParser;
import org.elasticsearch.index.query.functionscore.script.ScriptScoreFunctionParser;

import java.util.List;
Expand All @@ -42,6 +43,7 @@ public FunctionScoreModule() {
registerParser(GaussDecayFunctionParser.class);
registerParser(LinearDecayFunctionParser.class);
registerParser(ExponentialDecayFunctionParser.class);
registerParser(RandomScoreFunctionParser.class);
}

public void registerParser(Class<? extends ScoreFunctionParser> parser) {
Expand Down
@@ -0,0 +1,62 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.index.query.functionscore.random;

import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;

import java.io.IOException;

/**
* A function that computes a random score for the matched documents
*/
public class RandomScoreFunctionBuilder implements ScoreFunctionBuilder {

private Long seed = null;

public RandomScoreFunctionBuilder() {
}

@Override
public String getName() {
return RandomScoreFunctionParser.NAMES[0];
}

/**
* Sets the seed based on which the random number will be generated. Using the same seed is guaranteed to generate the same
* random number for a specific doc.
*
* @param seed The seed.
*/
public RandomScoreFunctionBuilder seed(long seed) {
this.seed = seed;
return this;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(getName());
if (seed != null) {
builder.field("seed", seed.longValue());
}
return builder.endObject();
}

}
@@ -0,0 +1,78 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/



package org.elasticsearch.index.query.functionscore.random;

import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.function.RandomScoreFunction;
import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.lucene.search.function.ScriptScoreFunction;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.query.functionscore.ScoreFunctionParser;
import org.elasticsearch.script.SearchScript;

import java.io.IOException;
import java.util.Map;

/**
*
*/
public class RandomScoreFunctionParser implements ScoreFunctionParser {

public static String[] NAMES = { "random_score", "randomScore" };

@Inject
public RandomScoreFunctionParser() {
}

@Override
public String[] getNames() {
return NAMES;
}

@Override
public ScoreFunction parse(QueryParseContext parseContext, XContentParser parser) throws IOException, QueryParsingException {

long seed = -1;

String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if ("seed".equals(currentFieldName)) {
seed = parser.longValue();
} else {
throw new QueryParsingException(parseContext.index(), NAMES[0] + " query does not support [" + currentFieldName + "]");
}
}
}

if (seed == -1) {
seed = parseContext.nowInMillis();
}

return new RandomScoreFunction(seed);
}
}
Expand Up @@ -28,7 +28,7 @@
import java.util.Map;

/**
* A query that uses a script to compute or influence the score of documents
* A function that uses a script to compute or influence the score of documents
* that match with the inner query or filter.
*/
public class ScriptScoreFunctionBuilder implements ScoreFunctionBuilder {
Expand Down

0 comments on commit c93eae8

Please sign in to comment.