繁体   English   中英

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

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

标题可能有点模糊,但这是我所拥有的(在私有化代码中):

包含一些字段的类,包括BigDecimal和Date:

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

  // Getters and Setters
}

在另一个类中,我有一个这些对象的列表(即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 ,然后使用! .anyMatch检查每个Pair<MyObj> 但我真的不想为此目的创建一个Pair类,如果可能的话。

是否有一种方法可以使用.reduce或其他东西来遍历MyObj对来检查它们? 这里最好的方法是使用Java 8流检查列表中MyObj所有日期和百分比是否按正确顺序排列?

另一种可能性是按日期排序列表,然后检查它们是否全部按百分比排序,如果这比检查两个字段是同一时间更容易。 不过,比较MyObj对百分比的问题仍然存在。

(PS:我将它用于com.vaadin.server.SerializablePredicate<MyObj> validator ,我更喜欢Java 8 lambda,因为我还使用了一些用于其他验证器,因此它更符合其他验证器在我的问题中,Java 8 lambda更像是一个偏好而不是要求。)

好吧,如果你想要一个短路操作,我不认为使用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;
}

并通过以下方式调用:

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

我想你几乎就是试图使用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使用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));

或者在ListNotOrdered类中使用方法引用:

isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)

由于您已经提到过您不想为此创建单独的类Pairs ,因此可以使用内置类来实现此目的: 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);

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

是的,您可以使用reduce来比较两个项目(尽管它不是最佳选择)。 当你发现一个乱序的项目时,你只需要创建一个新的“空”项,如下所示:

    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的答案。

我不认为这是一个应该使用流解决的问题。 Streams将映射和过滤应用于集合的元素(可能甚至将不同元素的处理分配到不同的CPU核心),然后再将它们收集到新的集合中或将它们减少到某种累积值。 您的问题涉及集合的不同元素之间的关系,这与流的目的相矛盾。 虽然可能存在涉及流的解决方案,但这些就像用钳子敲钉子钉入墙壁一样。 一个经典的循环对你来说非常好:找到一个元素的第一次出现破坏顺序并返回所需的结果! 因此,您甚至不需要创建一对。

与@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();

请注意,累积函数( BinaryOperator )应该是关联的,在这种情况下不是。 此外,它也不是短路的。

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM