Comment trier les résultats de recherche sur plusieurs champs à l'aide d'une fonction de pondération?

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

  •  03-07-2019
  •  | 
  •  

Question

J'ai un index Lucene où chaque document comporte plusieurs champs contenant des valeurs numériques. Maintenant, j'aimerais trier le résultat de la recherche sur une somme pondérée de ce champ. Par exemple:

field1=100
field2=002
field3=014

Et la fonction de pondération ressemble à:

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

Les résultats doivent être classés par f (d) d représente le document. La fonction de tri doit être non statique et peut différer d’une recherche à l’autre, car les facteurs de constante sont influencés par l’utilisateur qui effectue la recherche.

Quelqu'un at-il une idée de la façon de résoudre ce problème ou peut-être une idée de la manière d'atteindre cet objectif d'une autre manière?

Était-ce utile?

La solution

Vous pouvez essayer d'implémenter une correspondant ScoreDocComparator . Par exemple:

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);
    }

}

Voici un exemple de ScaledScoreDocComparator en action. Je crois que cela fonctionne dans mon test, mais je vous encourage à le prouver contre vos données.

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;

Bonus!

Il semble que les développeurs Lucene déconseillent l'interface ScoreDocComparator (il est actuellement obsolète dans le référentiel Subversion). Voici un exemple du ScaledScoreDocComparator modifié pour adhérer au successeur 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);
    }

}

L'utilisation de cette nouvelle classe est très similaire à l'originale, sauf que la définition de l'objet sort est un peu différente:

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);
            }
        }
    )
);

Autres conseils

Je pense qu’une façon de le faire serait d’accepter ces paramètres en tant que paramètres de votre fonction de tri:

nombre de champs, tableau de documents, liste des facteurs de pondération (en fonction du nombre de champs)

Calculez la fonction de pesée pour chaque document en stockant le résultat dans un tableau séparé, dans le même ordre que le tableau de documents. Ensuite, effectuez le tri souhaité (un tri rapide serait probablement préférable), en vous assurant de trier non seulement le tableau f (d), mais également le tableau de documents. Renvoyez le tableau de documents triés et vous avez terminé.

Implémentez votre propre classe de similarité et remplacez idf (terme, chercheur) . Dans cette méthode, vous pouvez renvoyer le score comme suit. if (term.field.equals ("quot1") {

    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;

Lorsque vous exécutez la requête, assurez-vous qu'elle figure dans tous les champs. Cette requête devrait ressembler à

  

champ1: terme champ2: terme champ3: terme

Le résultat final ajoutera également des pondérations basées sur la normalisation de la requête. Mais cela n’affectera pas le classement relatif des documents selon l’équation donnée par vous.

Créez un emballage qui contient l’évaluation et qui est comparable. Quelque chose comme:

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);
   }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top