使用 Java 比较器按另一个列表对对象列表进行排序
Sort object List by another List using Java Comparators
有两个输入列表如下:
inputA = [
{
name: "A",
age: 20
},
{
name: "B",
age: 30
},
{ name: "C",
age: 25
},
{ name: "D",
age: 28
}
]
inputB = ["D", "B"]
我的首选输出列表必须如下:
expectedOutput = [
{
name: "D",
age: 28
},
{
name: "B",
age: 30
},
{ name: "A",
age: 20
},
{ name: "C",
age: 25
}
]
到目前为止我所做的如下所示:
AtomicInteger count = new AtomicInteger();
Collections.sort(inputA, Comparator
.comparing(a ->
if (inputB.indexOf(a.getName()) > -1) {
return -1;
}
else {
return count.incrementAndGet();
})
.thenComparingInt(a -> a.getAge()));
我得到的输出如下
actualOutput = [
{
name: "D",
age: 28
},
{
name: "B",
age: 30
},
{ name: "C",
age: 25
},
{ name: "A",
age: 20
}
]
问题出在列表 inputB
中没有其名称的元素。该订单在 inputA
中没有原始订单。为了保持原来的顺序 { name: "A", age: 20 }
应该在 { name: "C", age: 25 }
之前
如何在使用比较器链接策略时解决这个问题?
更新
排序逻辑是,如果 inputA 具有名称与 inputB 列表相同的对象,则这些元素应位于 inputA 的顶部,然后这些元素必须按其年龄排序,同时保持 inputA 中其他元素的原始顺序不存在于 inputB
这不可能是重复的,因为这个问题试图比较两个列表,并根据第一个列表中对象的 属性 对公共元素进行排序,同时将其余元素保留在它们的列表中原始订单。
您的代码中的问题是第二个比较器在 inputA
中的所有项目上被激活。这意味着,即使不在 inputB
中的项目也会被排序,打破 inputA
的原始顺序。你可以这样避免它:
Collections.sort(inputA, Comparator.comparing(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
return 0;
}
return 1;
})
.thenComparingInt(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
return a.getAge();
}
return Integer.MAX_VALUE;
}));
但是,如果 inputB
未排序,我不确定是否需要特别调用 IndexOf()
两次。可能值得查看生成的字节码以查看其是否得到优化。
有一件事是,使用计数对比较没有任何影响。您只需要在比较操作中 return -1、0 或 1。因此,对计数执行线程安全增量没有任何价值。
另一方面,如果可以的话,在 class 中引入一个 priority
字段来定义 inputA
中的对象。然后你可以解决多次调用 indexOf()
的问题,例如:
Collections.sort(inputA, Comparator.comparing(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
a.setPriority(a.getAge());
return 0;
}
a.setPriority(Integer.MAX_VALUE);
return 1;
})
.thenComparingInt(a -> a.getPriority()));
如我所见,如果名称包含在 inputB
列表中,则需要按年龄对元素进行排序,如果 [=] 列表中不包含其余元素,则保持原样11=] 列表。按年龄排序的元素应该出现在结果的顶部,而未排序的元素应该出现在结果的底部。
如果这是你需要做的,你可以使用 Comparator.comparingInt
并让它 return 一个整数,它可以是年龄(对于第一种情况)或 Integer.MAX_VALUE
(对于另一种情况)。
您应该优化 inputB
的检查,以便它更快。为此,您可以从 inputB
.
创建一个 HashSet
这是代码:
Set<String> set = new HashSet<>(inputB);
Collections.sort(inputA, Comparator.comparingInt(a -> set.contains(a.getName()) ?
a.getAge() :
Integer.MAX_VALUE));
这有效,只要您的年龄不等于 Integer.MAX_VALUE
。
这个想法是你总是按年龄比较,但如果一个元素不属于 inputB
,你把年龄变成 Integer.MAX_VALUE
。这会产生两个效果:第一,它会让inputB
中不包含的元素出现在底部;其次,正如您总是 return Integer.MAX_VALUE
,inputA
列表的顺序被保留,因为 Collections.sort
实现了 稳定排序 。
有两个输入列表如下:
inputA = [
{
name: "A",
age: 20
},
{
name: "B",
age: 30
},
{ name: "C",
age: 25
},
{ name: "D",
age: 28
}
]
inputB = ["D", "B"]
我的首选输出列表必须如下:
expectedOutput = [
{
name: "D",
age: 28
},
{
name: "B",
age: 30
},
{ name: "A",
age: 20
},
{ name: "C",
age: 25
}
]
到目前为止我所做的如下所示:
AtomicInteger count = new AtomicInteger();
Collections.sort(inputA, Comparator
.comparing(a ->
if (inputB.indexOf(a.getName()) > -1) {
return -1;
}
else {
return count.incrementAndGet();
})
.thenComparingInt(a -> a.getAge()));
我得到的输出如下
actualOutput = [
{
name: "D",
age: 28
},
{
name: "B",
age: 30
},
{ name: "C",
age: 25
},
{ name: "A",
age: 20
}
]
问题出在列表 inputB
中没有其名称的元素。该订单在 inputA
中没有原始订单。为了保持原来的顺序 { name: "A", age: 20 }
应该在 { name: "C", age: 25 }
如何在使用比较器链接策略时解决这个问题?
更新 排序逻辑是,如果 inputA 具有名称与 inputB 列表相同的对象,则这些元素应位于 inputA 的顶部,然后这些元素必须按其年龄排序,同时保持 inputA 中其他元素的原始顺序不存在于 inputB
这不可能是重复的,因为这个问题试图比较两个列表,并根据第一个列表中对象的 属性 对公共元素进行排序,同时将其余元素保留在它们的列表中原始订单。
您的代码中的问题是第二个比较器在 inputA
中的所有项目上被激活。这意味着,即使不在 inputB
中的项目也会被排序,打破 inputA
的原始顺序。你可以这样避免它:
Collections.sort(inputA, Comparator.comparing(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
return 0;
}
return 1;
})
.thenComparingInt(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
return a.getAge();
}
return Integer.MAX_VALUE;
}));
但是,如果 inputB
未排序,我不确定是否需要特别调用 IndexOf()
两次。可能值得查看生成的字节码以查看其是否得到优化。
有一件事是,使用计数对比较没有任何影响。您只需要在比较操作中 return -1、0 或 1。因此,对计数执行线程安全增量没有任何价值。
另一方面,如果可以的话,在 class 中引入一个 priority
字段来定义 inputA
中的对象。然后你可以解决多次调用 indexOf()
的问题,例如:
Collections.sort(inputA, Comparator.comparing(a ->
{
if (inputB.indexOf(a.getName()) > -1)
{
a.setPriority(a.getAge());
return 0;
}
a.setPriority(Integer.MAX_VALUE);
return 1;
})
.thenComparingInt(a -> a.getPriority()));
如我所见,如果名称包含在 inputB
列表中,则需要按年龄对元素进行排序,如果 [=] 列表中不包含其余元素,则保持原样11=] 列表。按年龄排序的元素应该出现在结果的顶部,而未排序的元素应该出现在结果的底部。
如果这是你需要做的,你可以使用 Comparator.comparingInt
并让它 return 一个整数,它可以是年龄(对于第一种情况)或 Integer.MAX_VALUE
(对于另一种情况)。
您应该优化 inputB
的检查,以便它更快。为此,您可以从 inputB
.
HashSet
这是代码:
Set<String> set = new HashSet<>(inputB);
Collections.sort(inputA, Comparator.comparingInt(a -> set.contains(a.getName()) ?
a.getAge() :
Integer.MAX_VALUE));
这有效,只要您的年龄不等于 Integer.MAX_VALUE
。
这个想法是你总是按年龄比较,但如果一个元素不属于 inputB
,你把年龄变成 Integer.MAX_VALUE
。这会产生两个效果:第一,它会让inputB
中不包含的元素出现在底部;其次,正如您总是 return Integer.MAX_VALUE
,inputA
列表的顺序被保留,因为 Collections.sort
实现了 稳定排序 。