简体   繁体   English

Java 8+流:检查列表的对象实例的两个字段的顺序是否正确

[英]Java 8+ stream: Check if list is in the correct order for two fields of my object-instances

The title may be a bit vague, but here is what I have (in privatized code): 标题可能有点模糊,但这是我所拥有的(在私有化代码中):

A class with some fields, including a BigDecimal and Date: 包含一些字段的类,包括BigDecimal和Date:

class MyObj{
  private java.math.BigDecimal percentage;
  private java.util.Date date;
  // Some more irrelevant fields

  // Getters and Setters
}

In another class I have a list of these objects (ie java.util.List<MyObj> myList ). 在另一个类中,我有一个这些对象的列表(即java.util.List<MyObj> myList )。 What I want now is a Java 8 stream to check if the list is in the correct order of both dates and percentages for my validator. 我现在想要的是一个Java 8流来检查列表是否符合我的验证器的日期和百分比的正确顺序。

For example, the following list would be truthy: 例如,以下列表将是真实的:

[ MyObj { percentage = 25, date = 01-01-2018 },
  MyObj { percentage = 50, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

But this list would be falsey because the percentage aren't in the correct order: 但是这个列表是假的,因为百分比的顺序不正确:

[ MyObj { percentage = 25, date = 01-01-2018 },
  MyObj { percentage = 20, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

And this list would also be falsey because the dates aren't in the correct order: 此列表也将是假的,因为日期的顺序不正确:

[ MyObj { percentage = 25, date = 10-03-2018 },
  MyObj { percentage = 50, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

One possible solution might be creating Pairs like this and then using an ! 一种可能的解决方案可能是创建像这样的 Pairs ,然后使用! and .anyMatch checking each individual Pair<MyObj> . .anyMatch检查每个Pair<MyObj> But I don't really want to create a Pair class just for this purpose if possible. 但我真的不想为此目的创建一个Pair类,如果可能的话。

Is there perhaps a way to use .reduce or something to loop over pairs of MyObj to check them? 是否有一种方法可以使用.reduce或其他东西来遍历MyObj对来检查它们? What would be the best approach here to check if all dates and percentages of the MyObj in my list are in the correct order using Java 8 stream? 这里最好的方法是使用Java 8流检查列表中MyObj所有日期和百分比是否按正确顺序排列?

Another possibility is perhaps sorting the list by date, and then checking if they are all in order of percentage, if that's easier than checking both fields are the same time. 另一种可能性是按日期排序列表,然后检查它们是否全部按百分比排序,如果这比检查两个字段是同一时间更容易。 The same issue with comparing pairs of MyObj for the percentage still remains, though. 不过,比较MyObj对百分比的问题仍然存在。

(PS: I will use it for a com.vaadin.server.SerializablePredicate<MyObj> validator , and I prefer a Java 8 lambda because I've also used some for the other validators, so it would be more in line with the rest of the code. The Java 8 lambda is more a preference than requirement in my question however.) (PS:我将它用于com.vaadin.server.SerializablePredicate<MyObj> validator ,我更喜欢Java 8 lambda,因为我还使用了一些用于其他验证器,因此它更符合其他验证器在我的问题中,Java 8 lambda更像是一个偏好而不是要求。)

Well if you want a short-circuiting operation, I don't think an easy solution using stream-api exists... I propose a simpler one, first define a method that in a short-circuiting way will tell you if your List is sorted or not, based on some parameter: 好吧,如果你想要一个短路操作,我不认为使用stream-api的简单解决方案......我建议一个更简单的方法,首先定义一个方法,以短路的方式告诉你你的List是是否排序,基于一些参数:

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

And calling it via: 并通过以下方式调用:

 System.out.println(
          isSorted(myList, MyObj::getPercentage) && 
          isSorted(myList, MyObj::getDate));

I think you are almost there by trying to use Stream.anyMatch . 我想你几乎就是试图使用Stream.anyMatch You can accomplish it like this: 你可以像这样完成它:

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

We can use IntStream.range to iterate over the elements of the list using an index. 我们可以使用IntStream.range使用IntStream.range迭代列表的元素。 This way we can refer to any element in the list, eg the previous to compare it. 这样我们可以引用列表中的任何元素,例如前面的比较它。

EDIT adding a more generic version: 编辑添加更通用的版本:

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

This can be called as follows using the above predicate: 使用上面的谓词可以如下调用:

isNotOrderedAccordingTo(myList1, (before, after) -> isNotOrdered(before, after));

Or using method reference in a class ListNotOrdered : 或者在ListNotOrdered类中使用方法引用:

isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)

Since you've mentioned that you wouldn't want to create a separate class Pairs for this, You can use an inbuilt class for such purpose: AbstractMap.SimpleEntry . 由于您已经提到过您不想为此创建单独的类Pairs ,因此可以使用内置类来实现此目的: AbstractMap.SimpleEntry

You can make a BiPredicate which checks for both your comparing conditions & use that to compare all the pairs. 您可以制作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)));

The above solution with comparator: 以上解决方案与比较器:

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

Here is the solution by pairMap in StreamEx 这是pairMappairMap的解决方案

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

Yes, you can use reduce to compare two items (though it is not the best option). 是的,您可以使用reduce来比较两个项目(尽管它不是最佳选择)。 You just need to create a new "empty" item as a result when you find an item out of order, like this: 当你发现一个乱序的项目时,你只需要创建一个新的“空”项,如下所示:

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

This will print true only if both of your conditions are satisfied, false otherwise. 这将打印true只有当您的两个条件都满足, false否则。

Of course, you could make the code nicer by including some auxiliary methods in MyObj : 当然,您可以通过在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);

Bear in mind that this is not short-circuiting, ie it will traverse the whole list even after it finds an unordered item. 请记住,这不是短路,即即使在找到无序项目之后它也会遍历整个列表。 The only way to get out of the stream early while using reduce would be to throw a RuntimeException the moment you find the first unordered item, and that would be using exceptions to control program flow, which is considered bad practice. 在使用reduce尽早离开流的唯一方法是在找到第一个无序项时抛出RuntimeException ,这将使用异常来控制程序流,这被认为是不好的做法。 Still, I wanted to show you that you can indeed use reduce for this purpose. 不过,我想告诉你,你确实可以将reduce用于此目的。

For a short-circuiting approach that will finish as soon as it finds the first out of place item, take a look at @LuCio's answer. 对于短路方法,一旦找到第一个不合适的项目就会完成,请看看@LuCio的答案。

I don't think that this is a problem that should be solved using streams. 我不认为这是一个应该使用流解决的问题。 Streams apply mappings and filterings to the elements of a collection independently (probably even distributing the treatment of different elements to different CPU cores) before collecting them into a new collection again or reducing them to some sort of accumulated value. Streams将映射和过滤应用于集合的元素(可能甚至将不同元素的处理分配到不同的CPU核心),然后再将它们收集到新的集合中或将它们减少到某种累积值。 Your problem involves relations between different elements of a collection which contradicts the purpose of a stream. 您的问题涉及集合的不同元素之间的关系,这与流的目的相矛盾。 While there may be solutions involving streams those would be like hammering a nail into the wall with pliers. 虽然可能存在涉及流的解决方案,但这些就像用钳子敲钉子钉入墙壁一样。 A classic loop will be perfectly fine for you: find the first occurence of an element breaking the order and return the desired result! 一个经典的循环对你来说非常好:找到一个元素的第一次出现破坏顺序并返回所需的结果! Thus you wouldn't even need to create a pair. 因此,您甚至不需要创建一对。

Similar to @luis g.'s answer, you can also use reduce in combination with Optional (empty means unsorted) and a "minimal" MyObj as the identity: 与@luis g。的答案类似,您也可以将reduceOptional (空意味着未分类)和“最小” 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();

Note that the accumulating function ( BinaryOperator ) should be associative, which it is not in this case. 请注意,累积函数( BinaryOperator )应该是关联的,在这种情况下不是。 Furthermore it is also not short-circuiting. 此外,它也不是短路的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 检查Java-8-Stream中两个元素的顺序 - Check order of two elements in Java-8-Stream Java 8 Stream convert list to map, where map key is an object derived from two fields of the list? - Java 8 Stream convert list to map, where map key is an object derived from two fields of the list? Java 8流API,如何使用几个List字段将List收集到Object - Java 8 stream API, how to collect List to Object with several List fields 从 Java Object stream 中的两个列表加入列表 - Join list from two list in Java Object in stream 如何在此 Java 8+ 应用程序中正确使用 map 运算符以检索、修改和更新 DB 上的 object? - How to correctly use the map operator in this Java 8+ application in order to retrieve, modify and update an object on the DB? 多个过滤器应用于 Java 8+ 中的单个列表 - Multiple filters applied to a single list in Java 8+ 检查列表中两个对象中的字段 - Check the fields in two objects in list 使用Java中的可比较按两个字段升序和降序对对象列表进行排序 - Sort a list of objects by two fields ascending order and descending order using comparable in Java 检查Java 8中的两个“Instant”实例是否在同一天 - Check if two instances of “Instant” are on the same date in Java 8 如何将 stream 由 object 字段分成两组? - How to divide a stream by object fields into two sets?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM