使用Java对父项和子项进行排序
我有一个“Item”类,它包含以下字段(简而言之):id(与SQL Server上Item表的主键相关)、description、sequence(非null整数)和link(对父对象id的引用)可以为null 我想使用Java进行如下排序:使用Java对父项和子项进行排序,java,sorting,comparator,Java,Sorting,Comparator,我有一个“Item”类,它包含以下字段(简而言之):id(与SQL Server上Item表的主键相关)、description、sequence(非null整数)和link(对父对象id的引用)可以为null 我想使用Java进行如下排序: Id Sequence Link Description 1 1 null Item A 99 ..1 1 Son of A, first of the sequence 57
Id Sequence Link Description
1 1 null Item A
99 ..1 1 Son of A, first of the sequence
57 ..2 1 Son of A, second of the sequence
66 ..3 1 Son of A, third of the sequence
2 2 null Item B
3 3 null Item C
...
(Item it) -> it.getLink() == null ? it.getSequence() : idSeqMap.get(it.getLink())
(为了更好的可视化,我把这些点放进去)
也就是说,我希望某个项目的子项直接位于其父项之下,按“序列”字段排序
我尝试使用比较器,但失败了:
public class SequenceComparator implements Comparator<Item> {
@Override
public int compare(Item o1, Item o2) {
String x1 = o1.getSequence().toString();
String x2 = o2.getSequence().toString();
int sComp = x1.compareTo(x2);
if (sComp != 0) {
return sComp;
} else {
x1 = o1.getLink().toString();
x2 = o2.getLink() == null?"":o2.getLink().toString();
return x1.compareTo(x2);
}
}
}
公共类SequenceComparator实现Comparator{
@凌驾
公共整数比较(项目o1、项目o2){
字符串x1=o1.getSequence().toString();
字符串x2=o2.getSequence().toString();
int sComp=x1。与(x2)相比;
如果(sComp!=0){
返回sComp;
}否则{
x1=o1.getLink().toString();
x2=o2.getLink()==null?”:o2.getLink().toString();
返回x1.compareTo(x2);
}
}
}
我该怎么做呢?考虑到您的数据结构是一棵树(根节点为
null
),没有循环:
在找到共同的祖先之前,你必须在树上走到o1
和o2
。一旦您这样做了,请沿两个分支后退一步,以找到它们的相对顺序(使用序列
)
找到共同祖先可能很棘手,我不知道在线性时间内是否可能,但在O(n logn)时间内(n为分支长度)肯定可能找到新答案:我认为您不需要一个比较器来控制完整的排序,因为在排序子代时,您需要父代的序列,在比较器中,您无法轻松或自然地访问它 相反,我建议分几个步骤进行排序:
List.sort()
:
(此输出是通过toString
方法生成的,该方法在将具有父项的项目转换为字符串时打印点)
如果您不能使用Java8,我仍然相信上面提到的步骤的总体思路是可行的,只有一些步骤需要更多的代码
我删除了我以前的答案,因为我误解了getLink()
返回的内容,然后认为这个答案不值得挽救
编辑:
实际上,我忽略了收集器的文档中的这一部分。groupingBy()
:“没有关于返回的…列表
对象的…可变性的保证。”它仍然适用于我的Java 8。如果列表的不变性会阻止排序,那么解决方案是创建一个新的ArrayList
,其中包含相同的项
多亏了斯图尔特·马克斯的灵感,排序内部列表的比较器不必像上面那样笨拙。排序可以用以下压缩方式编写:
innerList.sort(Comparator.comparing(itm -> itm.getLink() == null ? null : itm.getSequence(),
Comparator.nullsFirst(Integer::compare)));
鉴于层次结构中只有两层,这可以归结为一种经典的多级排序。根据链接
字段是否为空,有两种项目,即父项和子项。诀窍在于,每一级的排序并不在特定字段上。相反,排序所依据的值取决于项目的类型
排序的第一级应在父值上。父项的父值是其序列,但子项的父值是其链接到的父项的序列。子项通过其id链接到父项,因此我们需要做的第一件事是建立从id到父节点序列值的映射:
Map<Integer, Integer> idSeqMap =
list.stream()
.filter(it -> it.getLink() == null)
.collect(Collectors.toMap(Item::getId, Item::getSequence));
第二级排序应在子值上。父项的子值为null,因此需要在任何非null值之前对null进行排序。子项的子值是其序列。用于获取子值的lambda表达式为:
(Item it) -> it.getLink() == null ? null : it.getSequence()
现在,我们可以使用Java8中引入的Comparator
helper函数来组合这些函数。结果可以直接传递到List.sort()
方法
list.sort(Comparator.comparingInt((Item it) -> it.getLink() == null ? it.getSequence() : idSeqMap.get(it.getLink()))
.thenComparing((Item it) -> it.getLink() == null ? null : it.getSequence(),
Comparator.nullsFirst(Integer::compare))
.thenComparingInt(Item::getId));
第一级排序非常简单;只需将第一个lambda表达式(提取父值)传递给Comparator.comparingit
第二级排序有点棘手。我假设getLink()
的结果是一个可为空的整数。首先,我们必须使用第二个lambda表达式提取子值。这会产生一个可为空的值,因此如果我们将其传递给然后进行比较
,我们将得到一个NullPointerException
。相反,然后比较
允许我们传递一个辅助比较器。我们将使用它来处理空值。对于这个二级比较器,我们通过
Comparator.nullsFirst(Integer::compare)
这将比较Integer
对象,首先排序空值,然后使用Integer.compare
方法依次比较非空值
list.sort(Comparator.comparingInt((Item it) -> it.getLink() == null ? it.getSequence() : idSeqMap.get(it.getLink()))
.thenComparing((Item it) -> it.getLink() == null ? null : it.getSequence(),
Comparator.nullsFirst(Integer::compare))
.thenComparingInt(Item::getId));
最后,我们比较id值作为最后手段。如果您仅将此比较器用于排序,则此选项是可选的;重复项将彼此相邻。但是,如果将此比较器用于树集
,则需要确保不同的项之间的比较永远不会相等。假设数据库id值足以区分所有唯一项。为什么不直接在SQL中对其排序?事实上,这会容易得多,因为您正在尝试对数字上的项目进行排序。这是一个。你在找。我需要用Java做这个。这不是我的决定
Comparator.nullsFirst(Integer::compare)