Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

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 (i.e. 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.

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 ! and .anyMatch checking each individual Pair<MyObj>. But I don't really want to create a Pair class just for this purpose if possible.

Is there perhaps a way to use .reduce or something to loop over pairs of MyObj to check them? 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?

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.

(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.)

like image 967
Kevin Cruijssen Avatar asked Aug 24 '18 09:08

Kevin Cruijssen


2 Answers

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:

 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));
like image 187
Eugene Avatar answered Sep 21 '22 21:09

Eugene


I think you are almost there by trying to use 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. This way we can refer to any element in the list, e.g. 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:

isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
like image 30
LuCio Avatar answered Sep 17 '22 21:09

LuCio