在Lucene.Net 3.0.3

时间:2016-11-22 14:46:41

标签: c# lucene.net

我正在寻找一种方法来自定义我的Lucene.Net结果,我放置null - 底部的值(文档上不存在字段),无论排序的方向(升序或降序)

以下数据总结了情况和想要的结果:

data in index    wanted sort result
data             desc    asc
----             ----    ----
100              400     100
400              300     300
null             100     400
300              null    null

我的情况是,我有一些产品并非所有产品都有价格。当升序排序时,我首先想要最便宜的产品,而不是没有价格的产品(这是预期的默认行为)。没有价格的产品仍然应该在结果中,但最后,因为这些在价格排序时最不相关。

我已经看了很多谷歌,我还没有真正找到你如何在 Lucene.Net 3.0.3 中实现自定义排序的任何答案。

我发现的最好的例子是this answer,这似乎指向了我正在寻找的方向。但答案是陈旧的,它所引用的ScoreDocComparator似乎是原始来源中的deprecated,因此也是Lucene的当前版本3.0.3 。净。 原始项目将FieldComparator称为替换,但这似乎比ScoreDocComparator要复杂得多(许多方法需要实现/覆盖,许多方法可以从继承中获益)重复的实现),我怀疑这是正确的道路吗?

理想情况下,我想为int / long字段实现一些通用的东西,它可以像在SortField对象中一样使用fieldname,因为我希望将来有更多的字段可以使这种自定义排序行为受益。

我认为实现是在Sort / SortField类的使用周围完成的,所以我的结束使用代码可能是这样的:

var sort = new Sort(new MyNullLastSortField("Price", SortField.INT, reverse));

但也许这也是错误的方式? SortField有一个构造函数,它以FieldComparator作为参数,但我似乎无法理解它是如何构造和实现的,以及来自索引的实际数据值流入的位置和进行。

非常感谢任何帮助我指向正确方向的帮助(最好带有示例代码)。

我的故障转移解决方案(不是首选)将向索引添加两个字段,仅用于进行排序,在插入时手动处理空值并在降序情况下将它们设置为-1在升序的情况下到9999999。然后通常使用具有价格和方向的特定字段名称的字段进行排序。

1 个答案:

答案 0 :(得分:1)

好奇心得到了我的好处。这是一个解决方案(有警告)

完整来源位于https://github.com/AndyPook/SO_CustomSort-40744865

添加可为空的int的扩展方法。 NumericField使用编码存储值,我不想进入,所以我只使用了一个标记值。

public static class NumericFieldExtensions
{
    public static NumericField SetIntValue(this NumericField f, int? value)
    {
        if (value.HasValue)
            f.SetIntValue(value.Value);
        else
            f.SetIntValue(int.MinValue);

        return f;
    }
}

"了解"定点。它只是lucene IntComparator的副本sealed,因此要复制。寻找int.MinValue以查看差异。

public class NullableIntComparator : FieldComparator
{
    private int[] values;
    private int[] currentReaderValues;
    private string field;
    private IntParser parser;
    private int bottom; // Value of bottom of queue
    private bool reversed;

    public NullableIntComparator(int numHits, string field, Parser parser, bool reversed)
    {
        values = new int[numHits];
        this.field = field;
        this.parser = (IntParser)parser;
        this.reversed = reversed;
    }

    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 = values[slot1];
        int v2 = values[slot2];

        if (v1 == int.MinValue)
            return reversed ? -1 : 1;
        if (v2 == int.MinValue)
            return reversed ? 1 : -1;

        if (v1 > v2)
        {
            return 1;
        }
        else if (v1 < v2)
        {
            return -1;
        }
        else
        {
            return 0;
        }
    }

    public override int CompareBottom(int doc)
    {
        if (bottom == int.MinValue)
            return reversed ? -1 : 1;

        // TODO: there are sneaky non-branch ways to compute
        // -1/+1/0 sign
        // Cannot return bottom - values[slot2] because that
        // may overflow
        int v2 = currentReaderValues[doc];

        if (v2 == int.MinValue)
            return reversed ? 1 : -1;

        if (bottom > v2)
        {
            return 1;
        }
        else if (bottom < v2)
        {
            return -1;
        }
        else
        {
            return 0;
        }
    }

    public override void Copy(int slot, int doc)
    {
        values[slot] = currentReaderValues[doc];
    }

    public override void SetNextReader(IndexReader reader, int docBase)
    {
        currentReaderValues = FieldCache_Fields.DEFAULT.GetInts(reader, field, parser);
    }

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

    public override IComparable this[int slot] => values[slot];
}

最后一个FieldComparatorSource来定义自定义排序

public class NullableIntFieldCompatitorSource : FieldComparatorSource
{
    public override FieldComparator NewComparator(string fieldname, int numHits, int sortPos, bool reversed)
    {
        return new NullableIntComparator(numHits, fieldname, FieldCache_Fields.NUMERIC_UTILS_INT_PARSER, reversed);
    }
}

一些测试。了解Sort是如何创建插件的。{/ p>

    private class DataDoc
    {
        public int ID { get; set; }
        public int? Data { get; set; }
    }

    private IEnumerable<DataDoc> Search(Sort sort)
    {
        var result = searcher.Search(new MatchAllDocsQuery(), null, 99, sort);

        foreach (var topdoc in result.ScoreDocs)
        {
            var doc = searcher.Doc(topdoc.Doc);
            int id = int.Parse(doc.GetFieldable("id").StringValue);
            int data = int.Parse(doc.GetFieldable("data").StringValue);

            yield return new DataDoc
            {
                ID = id,
                Data = data == int.MinValue ? (int?)null : data
            };
        }
    }

    [Fact]
    public void SortAscending()
    {
        var sort = new Sort(new SortField("data", new NullableIntFieldCompatitorSource()));

        var result = Search(sort).ToList();

        Assert.Equal(4, result.Count);
        Assert.Equal(new int?[] { 100, 300, 400, null }, result.Select(x => x.Data));
    }


    [Fact]
    public void SortDecending()
    {
        var sort = new Sort(new SortField("data", new NullableIntFieldCompatitorSource(),true));

        var result = Search(sort).ToList();

        Assert.Equal(4, result.Count);
        Assert.Equal(new int?[] { 400, 300, 100, null }, result.Select(x => x.Data));
    }

请注意

  • 每个doc 必须包含一个&#34;数据&#34;具有有效int的字段。你不能省略字段
  • 您需要使NullableIntFieldCompatitorSource更复杂,以便为您的字段名称返回正确的比较符。
  • 您需要为其他数字类型创建比较器。请参阅https://github.com/apache/lucenenet/blob/3.0.3/src/core/Search/FieldComparator.cs
  • 如果您不想使用标记值,则需要进入NumericField并找出如何编码null。但这意味着要进入其他几个班级