Lucene 2.9.2: как показать результаты в случайном порядке? - PullRequest
5 голосов
/ 26 августа 2011

По умолчанию Lucene возвращает результаты запроса в порядке релевантности (оценки).Вы можете передать поле сортировки (или несколько), затем результаты сортируются по этому полю.

Сейчас я ищу хорошее решение для получения результатов поиска в случайном порядке .

Плохой подход:
Конечно, я мог бы взять ВСЕ результаты и затем перетасовать коллекцию, но в случае 5 результатов поиска Mio это не очень хорошо работает.

Изящный постраничный подход:
При таком подходе вы сможете сказать Люсене следующее:
a) Дайте мне результаты с 1 по 10 из 5Mio в случайном порядке
б) Затем дайте мне от 11 до 20 (основываясь на той же случайной последовательности, которая используется в а).
в) Просто для пояснения: если вы дважды вызываете а), вы получаете одинаковые случайные элементы.

Как вы можете реализовать этот подход ??


Обновление от 27 июля 2012: знать, что решение, описанное здесь для Lucene 2.9.x, работает неправильно.Использование RandomOrderScoreDocComparator приведет к получению определенных результатов дважды в результирующем списке.

Ответы [ 2 ]

7 голосов
/ 26 августа 2011

Вы можете написать пользовательский FieldComparator:

public class RandomOrderFieldComparator extends FieldComparator<Integer> {

    private final Random random = new Random();

    @Override
    public int compare(int slot1, int slot2) {
        return random.nextInt();
    }

    @Override
    public int compareBottom(int doc) throws IOException {
        return random.nextInt();
    }

    @Override
    public void copy(int slot, int doc) throws IOException {
    }

    @Override
    public void setBottom(int bottom) {
    }

    @Override
    public void setNextReader(IndexReader reader, int docBase) throws IOException {
    }

    @Override
    public Integer value(int slot) {
        return random.nextInt();
    }

}

Это не потребляет ввода-вывода при перетасовке результатов.Вот мой пример программы, которая демонстрирует, как вы используете это:

public static void main(String... args) throws Exception {
    RAMDirectory directory = new RAMDirectory();

    Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_33);

    IndexWriter writer = new IndexWriter(
            directory,
            new IndexWriterConfig(Version.LUCENE_33, analyzer).setOpenMode(OpenMode.CREATE_OR_APPEND)
        );

    Document alice = new Document();
    alice.add( new Field("name", "Alice", Field.Store.YES, Field.Index.ANALYZED) );
    writer.addDocument( alice );

    Document bob = new Document();
    bob.add( new Field("name", "Bob", Field.Store.YES, Field.Index.ANALYZED) );
    writer.addDocument( bob );

    Document chris = new Document();
    chris.add( new Field("name", "Chris", Field.Store.YES, Field.Index.ANALYZED) );
    writer.addDocument( chris );

    writer.close();


    IndexSearcher searcher = new IndexSearcher( directory );



    for (int pass = 1; pass <= 10; pass++) {
        Query query = new MatchAllDocsQuery();

        Sort sort = new Sort(
                new SortField(
                        "",
                        new FieldComparatorSource() {

                            @Override
                            public FieldComparator<Integer> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
                                return new RandomOrderFieldComparator();
                            }

                        }
                    )
            );

        TopFieldDocs topFieldDocs = searcher.search( query, 10, sort );

        System.out.print("Pass #" + pass + ":");
        for (int i = 0; i < topFieldDocs.totalHits; i++) {
            System.out.print( " " + topFieldDocs.scoreDocs[i].doc );
        }
        System.out.println();
    }
}

Это приводит к следующему выводу:

Pass #1: 1 0 2
Pass #2: 1 0 2
Pass #3: 0 1 2
Pass #4: 0 1 2
Pass #5: 0 1 2
Pass #6: 1 0 2
Pass #7: 0 2 1
Pass #8: 1 2 0
Pass #9: 2 0 1
Pass #10: 0 2 1

Бонус!Для тех из вас, кто оказался в ловушке в Lucene 2

public class RandomOrderScoreDocComparator implements ScoreDocComparator {

    private final Random random = new Random();

    public int compare(ScoreDoc i, ScoreDoc j) {
        return random.nextInt();
    }

    public Comparable<?> sortValue(ScoreDoc i) {
        return Integer.valueOf( random.nextInt() );
    }

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

}

Все, что вам нужно изменить, это объект Sort:

Sort sort = new Sort(
    new SortField(
        "",
        new SortComparatorSource() {
            public ScoreDocComparator newComparator(IndexReader reader, String fieldName) throws IOException {
                return new RandomOrderScoreDocComparator();
            }
        }
    )
);
2 голосов
/ 29 августа 2012

Вот мое решение, которое до сих пор доказывало, что позволяет избежать дублирования результатов. Это на C # (для Lucene.Net), но я запустил его из примера Java, поэтому его легко конвертировать обратно.

Уловка, которую я использовал, заключалась в уникальном идентификаторе каждого запроса, который остается неизменным, пока пользователь нажимает на нумерацию страниц. У меня уже был этот уникальный идентификатор, который я использовал для отчетности. Я инициализирую его, когда пользователь нажимает кнопку поиска, а затем передаю его в зашифрованном виде в строке запроса. Наконец, он передается как начальный параметр в RandomOrderFieldComparatorSource (на самом деле это ID.GetHashCode ()).

Это означает, что я использую одну и ту же серию случайных чисел для одного и того же поиска, поэтому каждый docId получает одинаковый индекс сортировки, даже когда пользователи переходят на другие страницы результатов.

Еще одно примечание: slots может быть вектором, равным размеру страницы.

public class RandomOrderFieldComparator : FieldComparator
{
    Random random;
    List<int> slots = new List<int>();
    Dictionary<int, int> docSortIndeces = new Dictionary<int, int>();

    public RandomOrderFieldComparator(int? seed)
    {
        random = seed == null ? new Random() : new Random(seed.Value);
    }

    private int bottom; // Value of bottom of queue

    public override int Compare(int slot1, int slot2)
    {
        // TODO: there are sneaky non-branch ways to compute
        // -1/+1/0 sign
        // Cannot return values[slot1] - values[slot2] because that
        // may overflow
        int v1 = slots[slot1];
        int v2 = slots[slot2];
        if (v1 > v2) {
            return 1;
        } else if (v1 < v2) {
            return -1;
        } else {
            return 0;
        }
    }

    public override int CompareBottom(int doc)
    {
        // TODO: there are sneaky non-branch ways to compute
        // -1/+1/0 sign
        // Cannot return bottom - values[slot2] because that
        // may overflow
        int v2 = GetDocSortingIndex(doc);
        if (bottom > v2) {
            return 1;
        } else if (bottom < v2) {
            return -1;
        } else {
            return 0;
        }
    }

    public override void Copy(int slot, int doc)
    {
        if (slots.Count > slot) {
            slots[slot] = GetDocSortingIndex(doc);
        } else {
            slots.Add(GetDocSortingIndex(doc));
        }
        //slots[slot] = GetDocSortingIndex(doc);
    }

    public override void SetBottom(int bottom)
    {
        this.bottom = slots[bottom];
    }

    public override System.IComparable Value(int slot)
    {
        return (System.Int32)slots[slot];
    }

    public override void SetNextReader(IndexReader reader, int docBase)
    {
    }

    int GetDocSortingIndex(int docId)
    {
        if (!docSortIndeces.ContainsKey(docId))
            docSortIndeces[docId] = random.Next(int.MinValue, int.MaxValue);

        return docSortIndeces[docId];
    }
}
...