Lucene.Net 3.0.3 中空值的自定义排序
Custom sorting of null values in Lucene.Net 3.0.3
我正在寻找一种自定义排序 Lucene.Net 结果的方法,无论方向如何(升序或降序),我都将 null
-值(文档中不存在的字段)放在底部) 之类的。
以下数据总结了情况和想要的结果:
data in index wanted sort result
data desc asc
---- ---- ----
100 400 100
400 300 300
null 100 400
300 null null
我的情况是有些产品并非所有产品都有价格。升序排序时,我首先想要最便宜的产品,而不是没有价格的产品(这是预期的默认行为)。没有价格的产品应该仍然在结果中,但在最后,因为这些在按价格排序时最不相关。
我已经对 google 进行了大量研究,但我还没有真正找到关于如何在 Lucene.Net 3.0.3[= 中实现自定义排序的任何答案47=].
我找到的最好的例子是原始来源中的 this answer that seems to point me in the direction I'm looking for. But the answer is old and the ScoreDocComparator
it is refering to, seems to be deprecated,因此也在 Lucene.Net 的 当前版本 3.0.3 中。
原始项目将 FieldComparator 作为替换,但这似乎比 ScoreDocComparator
更复杂(许多方法需要 implemented/overridden 并且许多可以从中受益继承而不是重复实现),我怀疑这是正确的方法吗?
理想情况下,我想为 int/long 字段实现一些通用的东西,它可以像 SortField 对象一样考虑字段名,因为我希望将来有更多字段可以受益于这种自定义排序行为。
我认为实现是围绕 Sort
/SortField
class 的用法完成的,所以我的结束用法代码可能类似于:
var sort = new Sort(new MyNullLastSortField("Price", SortField.INT, reverse));
但也许那也是错误的方式? SortField
有一个将 FieldComparator
作为参数的构造函数,但我似乎无法理解它是如何构造和实现的,以及索引中的实际数据值流入和流出的位置。
非常感谢任何指向正确方向的帮助(最好是示例代码)。
我的故障转移解决方案(不是首选)将在仅用于排序的索引中添加两个字段,在插入时手动处理空值并将它们设置为 -1降序情况下,升序情况下为 9999999。然后按价格和方向的特定字段名的字段正常排序。
好奇心打败了我。这是一个解决方案(有注意事项)
完整来源位于 https://github.com/AndyPook/SO_CustomSort-40744865
添加可为 null 的整数的扩展方法。 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;
}
}
"understands" 哨兵的自定义兼容器。它只是 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
以了解如何将其连接在一起。
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));
}
备注
- 每个文档必须包含一个"data"字段和一个有效的整数。你不能只省略字段
- 您需要使
NullableIntFieldCompatitorSource
更复杂,以便它 returns 成为您的字段名称的正确比较符。
- 您需要为其他数字类型创建比较器。参见 https://github.com/apache/lucenenet/blob/3.0.3/src/core/Search/FieldComparator.cs
- 如果您不想使用标记值,则需要进入
NumericField
并了解如何编码 null
。但这意味着要进入其他几个 类
我正在寻找一种自定义排序 Lucene.Net 结果的方法,无论方向如何(升序或降序),我都将 null
-值(文档中不存在的字段)放在底部) 之类的。
以下数据总结了情况和想要的结果:
data in index wanted sort result
data desc asc
---- ---- ----
100 400 100
400 300 300
null 100 400
300 null null
我的情况是有些产品并非所有产品都有价格。升序排序时,我首先想要最便宜的产品,而不是没有价格的产品(这是预期的默认行为)。没有价格的产品应该仍然在结果中,但在最后,因为这些在按价格排序时最不相关。
我已经对 google 进行了大量研究,但我还没有真正找到关于如何在 Lucene.Net 3.0.3[= 中实现自定义排序的任何答案47=].
我找到的最好的例子是原始来源中的 this answer that seems to point me in the direction I'm looking for. But the answer is old and the ScoreDocComparator
it is refering to, seems to be deprecated,因此也在 Lucene.Net 的 当前版本 3.0.3 中。
原始项目将 FieldComparator 作为替换,但这似乎比 ScoreDocComparator
更复杂(许多方法需要 implemented/overridden 并且许多可以从中受益继承而不是重复实现),我怀疑这是正确的方法吗?
理想情况下,我想为 int/long 字段实现一些通用的东西,它可以像 SortField 对象一样考虑字段名,因为我希望将来有更多字段可以受益于这种自定义排序行为。
我认为实现是围绕 Sort
/SortField
class 的用法完成的,所以我的结束用法代码可能类似于:
var sort = new Sort(new MyNullLastSortField("Price", SortField.INT, reverse));
但也许那也是错误的方式? SortField
有一个将 FieldComparator
作为参数的构造函数,但我似乎无法理解它是如何构造和实现的,以及索引中的实际数据值流入和流出的位置。
非常感谢任何指向正确方向的帮助(最好是示例代码)。
我的故障转移解决方案(不是首选)将在仅用于排序的索引中添加两个字段,在插入时手动处理空值并将它们设置为 -1降序情况下,升序情况下为 9999999。然后按价格和方向的特定字段名的字段正常排序。
好奇心打败了我。这是一个解决方案(有注意事项)
完整来源位于 https://github.com/AndyPook/SO_CustomSort-40744865
添加可为 null 的整数的扩展方法。 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;
}
}
"understands" 哨兵的自定义兼容器。它只是 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
以了解如何将其连接在一起。
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));
}
备注
- 每个文档必须包含一个"data"字段和一个有效的整数。你不能只省略字段
- 您需要使
NullableIntFieldCompatitorSource
更复杂,以便它 returns 成为您的字段名称的正确比较符。 - 您需要为其他数字类型创建比较器。参见 https://github.com/apache/lucenenet/blob/3.0.3/src/core/Search/FieldComparator.cs
- 如果您不想使用标记值,则需要进入
NumericField
并了解如何编码null
。但这意味着要进入其他几个 类