Java 8+ 流:检查我的 object-instances 的两个字段的列表顺序是否正确
Java 8+ stream: Check if list is in the correct order for two fields of my object-instances
标题可能有点模糊,但这是我所拥有的(私有化代码):
具有一些字段的 class,包括 BigDecimal 和日期:
class MyObj{
private java.math.BigDecimal percentage;
private java.util.Date date;
// Some more irrelevant fields
// Getters and Setters
}
在另一个 class 中,我列出了这些 objects(即 java.util.List<MyObj> myList
)。我现在想要的是一个 Java 8 流来检查列表的日期和百分比顺序是否正确。
例如,下面的列表是真实的:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
但是这个列表是错误的,因为百分比的顺序不正确:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 20, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
而且这个列表也是错误的,因为日期顺序不正确:
[ MyObj { percentage = 25, date = 10-03-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
一个可能的解决方案可能是创建 Pairs
like this,然后使用 !
和 .anyMatch
检查每个个体 Pair<MyObj>
。但如果可能的话,我真的不想为此目的创建一个 Pair
class。
有没有办法使用 .reduce
或其他方法来遍历成对的 MyObj
来检查它们?使用 Java 8 流检查列表中 MyObj
的所有日期和百分比是否按正确顺序排列的最佳方法是什么?
另一种可能是按日期对列表进行排序,然后检查它们是否都按百分比排序,如果这比同时检查两个字段更容易的话。不过,比较 MyObj
对的百分比问题仍然存在。
(PS:我将它用于 com.vaadin.server.SerializablePredicate<MyObj> validator
,我更喜欢 Java 8 lambda,因为我也将一些用于其他验证器,所以它会更符合代码的其余部分。然而,Java 8 lambda 在我的问题中更像是一个偏好而不是要求。)
我想您尝试使用 Stream.anyMatch
就差不多了。您可以这样完成:
private static boolean isNotOrdered(List<MyObj> myList) {
return IntStream.range(1, myList.size()).anyMatch(i -> isNotOrdered(myList.get(i - 1), myList.get(i)));
}
private static boolean isNotOrdered(MyObj before, MyObj after) {
return before.getPercentage().compareTo(after.getPercentage()) > 0 ||
before.getDate().compareTo(after.getDate()) > 0;
}
我们可以使用 IntStream.range
使用索引迭代列表的元素。这样我们就可以引用列表中的任何元素,例如上一个比较一下。
编辑 添加更通用的版本:
private static boolean isNotOrderedAccordingTo(List<MyObj> myList, BiPredicate<MyObj, MyObj> predicate) {
return IntStream.range(1, myList.size()).anyMatch(i-> predicate.test(myList.get(i - 1), myList.get(i)));
}
这可以使用上面的谓词按如下方式调用:
isNotOrderedAccordingTo(myList1, (before, after) -> isNotOrdered(before, after));
或者在 class ListNotOrdered
:
中使用方法引用
isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
是的,您可以使用 reduce
来比较两个项目(尽管这不是最佳选择)。当您发现某个项目顺序不对时,您只需要创建一个新的 "empty" 项目,如下所示:
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> {
if ((a.percentage == null && a.date == null) || // previous item already failed
a.percentage.compareTo(b.percentage) > 0 || // pct out of order
a.date.after(b.date)) { // date out of order
return new MyObj(null, null); // return new empty MyObj
} else {
return b;
}
})
.filter(a -> a.percentage != null || a.date != null)
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
仅当您的两个条件都满足时,才会打印 true
,否则 false
。
当然,你可以通过在MyObj
中包含一些辅助方法来使代码更好:
class MyObj {
// ...
public MyObj() {}
public static MyObj EMPTY = new MyObj();
public boolean isEmpty() {
return percentage == null && date == null;
}
public boolean comesAfter(MyObj other) {
return this.percentage.compareTo(other.percentage) > 0 ||
this.date.after(other.date);
}
}
// ...
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> (a.isEmpty() || a.comesAfter(b)) ? MyObj.EMPTY : b)
.filter(b -> !b.isEmpty())
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
请记住,这不是短路,即即使找到未排序的项目,它也会遍历整个列表。在使用 reduce
时尽早退出流的唯一方法是在找到第一个无序项时抛出 RuntimeException
,这将使用异常来控制程序流,这被认为不好的做法。不过,我还是想告诉你,你确实可以使用 reduce
来达到这个目的。
对于一旦找到第一个不合适的项目就会完成的短路方法,请查看@LuCio 的回答。
好吧,如果你想要一个短路操作,我认为使用 stream-api 的简单解决方案不存在......我建议一个更简单的方法,首先定义一个方法电路方式会根据某些参数告诉您列表是否已排序:
private static <T, R extends Comparable<? super R>> boolean isSorted(List<T> list, Function<T, R> f) {
Comparator<T> comp = Comparator.comparing(f);
for (int i = 0; i < list.size() - 1; ++i) {
T left = list.get(i);
T right = list.get(i + 1);
if (comp.compare(left, right) >= 0) {
return false;
}
}
return true;
}
并通过以下方式调用它:
System.out.println(
isSorted(myList, MyObj::getPercentage) &&
isSorted(myList, MyObj::getDate));
既然你已经提到你不想为此创建一个单独的 class Pairs
,你可以为此目的使用内置的 class:AbstractMap.SimpleEntry
.
您可以创建一个 BiPredicate
来检查您的比较条件并使用它来比较所有对。
BiPredicate<MyObj,MyObj> isIncorrectOrder = (o1,o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? wrongOrder : o1.getPercentage().compareTo(o2.getPercentage()) > 0;
};
boolean isNotSorted = IntStream.range(1,myObjs.size())
.anyMatch(i -> isIncorrectOrder.test(myObjs.get(i-1),myObjs.get(i)));
上述带有比较器的解决方案:
Comparator<MyObj> comparator = (o1, o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? 1 : o1.getPercentage().compareTo(o2.getPercentage());
};
Predicate<AbstractMap.SimpleEntry<MyObj,MyObj>> isIncorrectOrder = pair -> comparator.compare(pair.getKey(),pair.getValue()) > 0;
boolean isNotSorted = IntStream.range(1,myObjs.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<>(myObjs.get(i-1),myObjs.get(i)))
.anyMatch(isIncorrectOrder);
这是 pairMap
在 StreamEx
中的解决方案
StreamEx.of(1, 2, 3, 5).pairMap((a, b) -> a <= b).allMatch(e -> e); // true
StreamEx.of(1, 2, 5, 3).pairMap((a, b) -> a <= b).allMatch(e -> e); // false
// your example:
StreamEx.of(myList)
.pairMap((a, b) -> a.getPercentage().compareTo(b.getPercentage()) <= 0 && !a.getDate().after(b.getDate()))
.allMatch(e -> e);
我不认为这是一个应该使用流来解决的问题。 Streams 独立地将映射和过滤应用于集合的元素(甚至可能将不同元素的处理分配给不同的 CPU 核心),然后再将它们收集到新的集合中或将它们减少到某种累积值。您的问题涉及集合的不同元素之间的关系,这与流的目的相矛盾。虽然可能有涉及流的解决方案,但这些解决方案就像用钳子将钉子钉入墙上一样。经典循环非常适合您:找到第一次出现的打破顺序的元素和 return 所需的结果!因此,您甚至不需要创建一对。
类似于@luis g. 的回答,您还可以将 reduce
与 Optional
(空表示未排序)和 "minimal" MyObj
结合使用身份:
boolean isSorted = list.stream()
.map(Optional::of)
.reduce(Optional.of(new MyObj(BigDecimal.ZERO, Date.from(Instant.EPOCH))),
(left, right) -> left.flatMap(l -> right.map(r -> l.date.compareTo(r.date)<= 0 && l.percentage.compareTo(r.percentage) <= 0 ? r : null)))
.isPresent();
请注意,累加函数 (BinaryOperator
) 应该是结合函数,但在本例中不是。而且它也没有短路。
标题可能有点模糊,但这是我所拥有的(私有化代码):
具有一些字段的 class,包括 BigDecimal 和日期:
class MyObj{
private java.math.BigDecimal percentage;
private java.util.Date date;
// Some more irrelevant fields
// Getters and Setters
}
在另一个 class 中,我列出了这些 objects(即 java.util.List<MyObj> myList
)。我现在想要的是一个 Java 8 流来检查列表的日期和百分比顺序是否正确。
例如,下面的列表是真实的:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
但是这个列表是错误的,因为百分比的顺序不正确:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 20, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
而且这个列表也是错误的,因为日期顺序不正确:
[ MyObj { percentage = 25, date = 10-03-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
一个可能的解决方案可能是创建 Pairs
like this,然后使用 !
和 .anyMatch
检查每个个体 Pair<MyObj>
。但如果可能的话,我真的不想为此目的创建一个 Pair
class。
有没有办法使用 .reduce
或其他方法来遍历成对的 MyObj
来检查它们?使用 Java 8 流检查列表中 MyObj
的所有日期和百分比是否按正确顺序排列的最佳方法是什么?
另一种可能是按日期对列表进行排序,然后检查它们是否都按百分比排序,如果这比同时检查两个字段更容易的话。不过,比较 MyObj
对的百分比问题仍然存在。
(PS:我将它用于 com.vaadin.server.SerializablePredicate<MyObj> validator
,我更喜欢 Java 8 lambda,因为我也将一些用于其他验证器,所以它会更符合代码的其余部分。然而,Java 8 lambda 在我的问题中更像是一个偏好而不是要求。)
我想您尝试使用 Stream.anyMatch
就差不多了。您可以这样完成:
private static boolean isNotOrdered(List<MyObj> myList) {
return IntStream.range(1, myList.size()).anyMatch(i -> isNotOrdered(myList.get(i - 1), myList.get(i)));
}
private static boolean isNotOrdered(MyObj before, MyObj after) {
return before.getPercentage().compareTo(after.getPercentage()) > 0 ||
before.getDate().compareTo(after.getDate()) > 0;
}
我们可以使用 IntStream.range
使用索引迭代列表的元素。这样我们就可以引用列表中的任何元素,例如上一个比较一下。
编辑 添加更通用的版本:
private static boolean isNotOrderedAccordingTo(List<MyObj> myList, BiPredicate<MyObj, MyObj> predicate) {
return IntStream.range(1, myList.size()).anyMatch(i-> predicate.test(myList.get(i - 1), myList.get(i)));
}
这可以使用上面的谓词按如下方式调用:
isNotOrderedAccordingTo(myList1, (before, after) -> isNotOrdered(before, after));
或者在 class ListNotOrdered
:
isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
是的,您可以使用 reduce
来比较两个项目(尽管这不是最佳选择)。当您发现某个项目顺序不对时,您只需要创建一个新的 "empty" 项目,如下所示:
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> {
if ((a.percentage == null && a.date == null) || // previous item already failed
a.percentage.compareTo(b.percentage) > 0 || // pct out of order
a.date.after(b.date)) { // date out of order
return new MyObj(null, null); // return new empty MyObj
} else {
return b;
}
})
.filter(a -> a.percentage != null || a.date != null)
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
仅当您的两个条件都满足时,才会打印 true
,否则 false
。
当然,你可以通过在MyObj
中包含一些辅助方法来使代码更好:
class MyObj {
// ...
public MyObj() {}
public static MyObj EMPTY = new MyObj();
public boolean isEmpty() {
return percentage == null && date == null;
}
public boolean comesAfter(MyObj other) {
return this.percentage.compareTo(other.percentage) > 0 ||
this.date.after(other.date);
}
}
// ...
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> (a.isEmpty() || a.comesAfter(b)) ? MyObj.EMPTY : b)
.filter(b -> !b.isEmpty())
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
请记住,这不是短路,即即使找到未排序的项目,它也会遍历整个列表。在使用 reduce
时尽早退出流的唯一方法是在找到第一个无序项时抛出 RuntimeException
,这将使用异常来控制程序流,这被认为不好的做法。不过,我还是想告诉你,你确实可以使用 reduce
来达到这个目的。
对于一旦找到第一个不合适的项目就会完成的短路方法,请查看@LuCio 的回答。
好吧,如果你想要一个短路操作,我认为使用 stream-api 的简单解决方案不存在......我建议一个更简单的方法,首先定义一个方法电路方式会根据某些参数告诉您列表是否已排序:
private static <T, R extends Comparable<? super R>> boolean isSorted(List<T> list, Function<T, R> f) {
Comparator<T> comp = Comparator.comparing(f);
for (int i = 0; i < list.size() - 1; ++i) {
T left = list.get(i);
T right = list.get(i + 1);
if (comp.compare(left, right) >= 0) {
return false;
}
}
return true;
}
并通过以下方式调用它:
System.out.println(
isSorted(myList, MyObj::getPercentage) &&
isSorted(myList, MyObj::getDate));
既然你已经提到你不想为此创建一个单独的 class Pairs
,你可以为此目的使用内置的 class:AbstractMap.SimpleEntry
.
您可以创建一个 BiPredicate
来检查您的比较条件并使用它来比较所有对。
BiPredicate<MyObj,MyObj> isIncorrectOrder = (o1,o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? wrongOrder : o1.getPercentage().compareTo(o2.getPercentage()) > 0;
};
boolean isNotSorted = IntStream.range(1,myObjs.size())
.anyMatch(i -> isIncorrectOrder.test(myObjs.get(i-1),myObjs.get(i)));
上述带有比较器的解决方案:
Comparator<MyObj> comparator = (o1, o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? 1 : o1.getPercentage().compareTo(o2.getPercentage());
};
Predicate<AbstractMap.SimpleEntry<MyObj,MyObj>> isIncorrectOrder = pair -> comparator.compare(pair.getKey(),pair.getValue()) > 0;
boolean isNotSorted = IntStream.range(1,myObjs.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<>(myObjs.get(i-1),myObjs.get(i)))
.anyMatch(isIncorrectOrder);
这是 pairMap
在 StreamEx
StreamEx.of(1, 2, 3, 5).pairMap((a, b) -> a <= b).allMatch(e -> e); // true
StreamEx.of(1, 2, 5, 3).pairMap((a, b) -> a <= b).allMatch(e -> e); // false
// your example:
StreamEx.of(myList)
.pairMap((a, b) -> a.getPercentage().compareTo(b.getPercentage()) <= 0 && !a.getDate().after(b.getDate()))
.allMatch(e -> e);
我不认为这是一个应该使用流来解决的问题。 Streams 独立地将映射和过滤应用于集合的元素(甚至可能将不同元素的处理分配给不同的 CPU 核心),然后再将它们收集到新的集合中或将它们减少到某种累积值。您的问题涉及集合的不同元素之间的关系,这与流的目的相矛盾。虽然可能有涉及流的解决方案,但这些解决方案就像用钳子将钉子钉入墙上一样。经典循环非常适合您:找到第一次出现的打破顺序的元素和 return 所需的结果!因此,您甚至不需要创建一对。
类似于@luis g. 的回答,您还可以将 reduce
与 Optional
(空表示未排序)和 "minimal" MyObj
结合使用身份:
boolean isSorted = list.stream()
.map(Optional::of)
.reduce(Optional.of(new MyObj(BigDecimal.ZERO, Date.from(Instant.EPOCH))),
(left, right) -> left.flatMap(l -> right.map(r -> l.date.compareTo(r.date)<= 0 && l.percentage.compareTo(r.percentage) <= 0 ? r : null)))
.isPresent();
请注意,累加函数 (BinaryOperator
) 应该是结合函数,但在本例中不是。而且它也没有短路。