代码之家  ›  专栏  ›  技术社区  ›  jan

Lucene中空值的自定义排序。Net 3.0.3

  •  1
  • jan  · 技术社区  · 8 年前

    null -无论排序方向(升序或降序)如何,底部的值(文档上不存在字段)。

    以下数据总结了情况和预期结果:

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

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

    我已经在google上搜索了很多次,但对于如何在中实现自定义排序,我还没有找到任何答案 卢塞恩。Net 3.0.3 .

    我找到的最好的例子是 this answer 这似乎为我指明了我要寻找的方向。但答案是古老的 ScoreDocComparator 它指的是,似乎是 deprecated 在原始源中,因此也在 的Lucene.Net。 FieldComparator 作为替代,但这似乎比 计分比较器 (许多方法需要实现/重写,许多方法可能受益于继承而不是重复实现),我怀疑这是正确的路径吗?

    理想情况下,我希望为int/long字段实现一些通用的功能,其中可以考虑字段名,比如SortField对象,因为我希望将来会有更多的字段受益于这种自定义排序行为。

    我认为实现是围绕 Sort / SortField 类,因此我的最终使用代码可能类似于:

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

    但也许这也是错误的方式? 索特菲尔德 具有一个构造函数,该构造函数接受 FieldComparator 作为参数,但我似乎无法理解这是如何构造和实现的,以及索引中的实际数据值在哪里流入和流出。

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

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

    1 回复  |  直到 7 年前
        1
  •  1
  •   AndyPook    7 年前

    好奇心压倒了我。这里有一个解决方案(附带警告)

    完整来源如下: https://github.com/AndyPook/SO_CustomSort-40744865

    用于添加可空整数的扩展方法。NumericField使用编码来存储我不想进入的值,所以我只使用了一个sentinel值。

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

    最后,a 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

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