¿Cómo ordenar los resultados de búsqueda en múltiples campos usando una función de ponderación?

StackOverflow https://stackoverflow.com/questions/817998

  •  03-07-2019
  •  | 
  •  

Pregunta

Tengo un índice de Lucene donde cada documento tiene varios campos que contienen valores numéricos. Ahora me gustaría ordenar el resultado de la búsqueda en una suma ponderada de este campo. Por ejemplo:

field1=100
field2=002
field3=014

Y la función de ponderación se ve así:

f(d) = field1 * 0.5 + field2 * 1.4 + field3 * 1.8

Los resultados deben ordenarse por f (d) donde d representa el documento. La función de clasificación no debe ser estática y puede diferir de una búsqueda a otra porque los factores constantes están influenciados por el usuario que realiza la búsqueda.

¿Alguien tiene una idea de cómo resolver esto o tal vez una idea de cómo lograr este objetivo de otra manera?

¿Fue útil?

Solución

Puede intentar implementar un personalizado ScoreDocComparator . Por ejemplo:

public class ScaledScoreDocComparator implements ScoreDocComparator {

    private int[][] values;
    private float[] scalars;

    public ScaledScoreDocComparator(IndexReader reader, String[] fields, float[] scalars) throws IOException {
        this.scalars = scalars;
        this.values = new int[fields.length][];
        for (int i = 0; i < values.length; i++) {
            this.values[i] = FieldCache.DEFAULT.getInts(reader, fields[i]);
        }
    }

    protected float score(ScoreDoc scoreDoc) {
        int doc = scoreDoc.doc;

        float score = 0;
        for (int i = 0; i < values.length; i++) {
            int value = values[i][doc];
            float scalar = scalars[i];
            score += (value * scalar);
        }
        return score;
    }

    @Override
    public int compare(ScoreDoc i, ScoreDoc j) {
        float iScore = score(i);
        float jScore = score(j);
        return Float.compare(iScore, jScore);
    }

    @Override
    public int sortType() {
        return SortField.CUSTOM;
    }

    @Override
    public Comparable<?> sortValue(ScoreDoc i) {
        float score = score(i);
        return Float.valueOf(score);
    }

}

Aquí hay un ejemplo de ScaledScoreDocComparator en acción. Creo que funciona en mi prueba, pero le animo a que lo pruebe con sus datos.

final String[] fields = new String[]{ "field1", "field2", "field3" };
final float[] scalars = new float[]{ 0.5f, 1.4f, 1.8f };

Sort sort = new Sort(
    new SortField(
        "",
        new SortComparatorSource() {
            public ScoreDocComparator newComparator(IndexReader reader, String fieldName) throws IOException {
                return new ScaledScoreDocComparator(reader, fields, scalars);
            }
        }
    )
);

IndexSearcher indexSearcher = ...;
Query query = ...;
Filter filter = ...; // can be null
int nDocs = 100;

TopFieldDocs topFieldDocs = indexSearcher.search(query, filter, nDocs, sort);
ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;

Bono!

Parece que los desarrolladores de Lucene están despreciando la interfaz ScoreDocComparator (actualmente está en desuso en el repositorio de Subversion). Aquí hay un ejemplo del ScaledScoreDocComparator modificado para adherirse al sucesor de ScoreDocComparator , FieldComparator :

public class ScaledComparator extends FieldComparator {

    private String[] fields;
    private float[] scalars;
    private int[][] slotValues;
    private int[][] currentReaderValues;
    private int bottomSlot;

    public ScaledComparator(int numHits, String[] fields, float[] scalars) {
        this.fields = fields;
        this.scalars = scalars;

        this.slotValues = new int[this.fields.length][];
        for (int fieldIndex = 0; fieldIndex < this.fields.length; fieldIndex++) {
            this.slotValues[fieldIndex] = new int[numHits];
        }

        this.currentReaderValues = new int[this.fields.length][];
    }

    protected float score(int[][] values, int secondaryIndex) {
        float score = 0;

        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            int value = values[fieldIndex][secondaryIndex];
            float scalar = scalars[fieldIndex];
            score += (value * scalar);
        }

        return score;
    }

    protected float scoreSlot(int slot) {
        return score(slotValues, slot);
    }

    protected float scoreDoc(int doc) {
        return score(currentReaderValues, doc);
    }

    @Override
    public int compare(int slot1, int slot2) {
        float score1 = scoreSlot(slot1);
        float score2 = scoreSlot(slot2);
        return Float.compare(score1, score2);
    }

    @Override
    public int compareBottom(int doc) throws IOException {
        float bottomScore = scoreSlot(bottomSlot);
        float docScore = scoreDoc(doc);
        return Float.compare(bottomScore, docScore);
    }

    @Override
    public void copy(int slot, int doc) throws IOException {
        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            slotValues[fieldIndex][slot] = currentReaderValues[fieldIndex][doc];
        }
    }

    @Override
    public void setBottom(int slot) {
        bottomSlot = slot;
    }

    @Override
    public void setNextReader(IndexReader reader, int docBase, int numSlotsFull) throws IOException {
        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            String field = fields[fieldIndex];
            currentReaderValues[fieldIndex] = FieldCache.DEFAULT.getInts(reader, field);
        }
    }

    @Override
    public int sortType() {
        return SortField.CUSTOM;
    }

    @Override
    public Comparable<?> value(int slot) {
        float score = scoreSlot(slot);
        return Float.valueOf(score);
    }

}

Usar esta nueva clase es muy similar al original, excepto que la definición del objeto sort es un poco diferente:

final String[] fields = new String[]{ "field1", "field2", "field3" };
final float[] scalars = new float[]{ 0.5f, 1.4f, 1.8f };

Sort sort = new Sort(
    new SortField(
        "",
        new FieldComparatorSource() {
            public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
                return new ScaledComparator(numHits, fields, scalars);
            }
        }
    )
);

Otros consejos

Estoy pensando que una forma de hacerlo sería aceptarlos como parámetros para su función de clasificación:

número de campos, matriz de documentos, lista de factores de peso (en función del número de campos)

Calcule la función de pesaje para cada documento, almacenando el resultado en una matriz separada en el mismo orden que la matriz de documentos. Luego, realice cualquier ordenación que desee (la ordenación rápida probablemente sería la mejor), asegurándose de ordenar no solo la matriz f (d), sino también la matriz de documentos. Devuelva la matriz de documentos ordenados y listo.

Implemente su propia clase de similitud y anule método idf (Term, Searcher) . En este método, puede devolver la puntuación de la siguiente manera. if (term.field.equals (" field1 ") {

    if (term.field.equals("field1") {
        score = 0.5 * Integer.parseInt(term.text());
    } else if (term.field.equals("field2") {
        score = 1.4 * Integer.parseInt(term.text());
    } // and so on
    return score;

Cuando ejecute la consulta, asegúrese de que esté en todos los campos. Es decir, la consulta debería verse como

  

campo1: término campo2: término campo3: término

La puntuación final también agregará algunos pesos basados ??en la normalización de la consulta. Pero eso no afectará la clasificación relativa de los documentos según la ecuación dada por usted.

Cree un contenedor que contenga la calificación y sea comparable. Algo así como:

public void sort(Datum[] data) {
   Rating[] ratings = new Rating[data.length];
   for(int i=0;i<data.length;i++)
     rating[i] = new Rating(data[i]);
   Arrays.sort(rating);
   for(int i=0;i<data.length;i++)
     data[i] = rating[i].datum;
}

class Rating implements Comparable<Datum> {
   final double rating;
   final Datum datum;

   public Rating(Datum datum) {
      this.datum = datum;
      rating = datum.field1 * 0.5 + datum.field2 * 1.4 + datum.field3 * 1.8
   }

   public int compareTo(Datum d) {
      return Double.compare(rating, d.rating);
   }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top