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

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.


java.util.List<MyObj> myList



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.


Pairs


!


.anyMatch


Pair<MyObj>


Pair



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?


.reduce


MyObj


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


com.vaadin.server.SerializablePredicate<MyObj> validator





If I understand you correctly you want both fields to be primary keys in the sorting. That sounds impossible to me unless you combine them in some way. I can't see how that would be possble. or do I misunderstand?
– Jack Flamp
Aug 24 at 10:12






@JackFlamp as I understand it he just wants to check, i.e. dates or percentages being out of order could be possible but the validator should report them as invalid.
– Thomas
Aug 24 at 10:13





Could you elaborate a bit on how exactly you'd use SerializablePredicate (which btw isn't a JDK class)? I'm not sure you really have to use a stream.
– Thomas
Aug 24 at 10:17


SerializablePredicate





Can you clarify: Do you want to perform the sort? - or just validate that the existing order satisfies your requirements?
– tbsalling
Aug 24 at 10:21






So you want the sequence of percentages and the sequence of dates to be non-decreasing? I don't know why you started talking about sorting which has nothing to do with validating this and, as others have pointe dout, is impossible with this kind of comparison when certain elements are in the sequence.
– Giacomo Alzetta
Aug 24 at 14:41




7 Answers
7



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





if(comp.compare(left, right) < 0) continue; else return false; , really? Why not if(comp.compare(left, right) >= 0) return false; ?
– Holger
Aug 24 at 10:39



if(comp.compare(left, right) < 0) continue; else return false;


if(comp.compare(left, right) >= 0) return false;





Actually, it should only be if(comp.compare(left, right) > 0) return false;, as equal elements do not violate the sorted property. What’s the purpose of returning a Supplier<Boolean> instead of a boolean?
– Holger
Aug 24 at 10:43


if(comp.compare(left, right) > 0) return false;


Supplier<Boolean>


boolean





But you can simply write isSorted(...) && isSorted(...) instead, if you really want this, as isSorted(x.thenComparing(y)) is not the same as isSorted(x) && isSorted(y).
– Holger
Aug 24 at 10:49


isSorted(...) && isSorted(...)


isSorted(x.thenComparing(y))


isSorted(x) && isSorted(y)





I was misguided by the OP’s comparator example. Actually, isSorted(...) && isSorted(...) is intended. I don’t see any reason to use a Supplier here. Alternatively, you may accept a BiPredicate instead of a Comparator, to let the caller provide a function which checks both properties.
– Holger
Aug 24 at 11:13


isSorted(...) && isSorted(...)


Supplier


BiPredicate


Comparator





Having a Supplier<Boolean> is useless in this case since you evaluate the whole list to create that supplier, and evaluating the supplier is basically free (a constant value is returned). It just makes everything less readable. I don't think that part of the answer should be kept.
– Didier L
Aug 24 at 13:34


Supplier<Boolean>



I think you are almost there by trying to use Stream.anyMatch. You can accomplish it like this:


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


IntStream.range



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)





You can even omit the myList.size() > 1 && part, as anyMatch on the empty stream will already do the right thing.
– Holger
Aug 24 at 11:00



myList.size() > 1 &&


anyMatch





@Holger If the list has size 1, anyMatch will work but the inner call to myList.get(i) will cause an AIOOB exception. It is necessary for the list to have at least size 2 for this to work.
– Luis G.
Aug 24 at 11:08


anyMatch


myList.get(i)





@LuisG. No, just try it, if you don’t believe. IntStream.range(0, 1) and IntStream.range(1, 1) are empty streams, which will never call get on the List.
– Holger
Aug 24 at 11:11



IntStream.range(0, 1)


IntStream.range(1, 1)


get


List





Verified @Holger's suggestion. It applies to an empty list and a list with one element.
– LuCio
Aug 24 at 11:12






I ended up using Eugene's approach because it's generic and easier re-usable for other streams. Just verified your approach however, and it's also working like a charm, so I've upvoted. Thanks as well for answering.
– Kevin Cruijssen
Aug 24 at 11:32



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





I mainly meant creating a Pair class only for this purpose. Using a AbstractMap.SimpleEntry as pair like you do is completely fine by me, so upvoted as well. :)
– Kevin Cruijssen
Aug 24 at 12:18


Pair


AbstractMap.SimpleEntry





@KevinCruijssen :)
– Pankaj Singhal
Aug 24 at 12:20





@KevinCruijssen Provided an even smaller solution with BiPredicate
– Pankaj Singhal
Aug 24 at 12:25


BiPredicate



Here is the solution by pairMap in StreamEx


pairMap


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





@LuCio updated, thanks
– user_3380739
Aug 29 at 17:35



Yes, you can use reduce to compare two items (though it is not the best option). You just need to create a new "empty" item as a result when you find an item out of order, like this:


reduce


boolean fullyOrdered = myList.stream()
.reduce((a, b) -> // 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

// ...
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, i.e. 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. Still, I wanted to show you that you can indeed use reduce for this purpose.


reduce


RuntimeException


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.





This might not work for a parallel stream and breaks the reduce method contract because the accumulator is not associative.
– Alex
Aug 24 at 11:15





@Alex Fair points. As I said, this was just kind of a PoC showing that yeah, you can do that using reduce, though other answers are better.
– Luis G.
Aug 24 at 12:15


reduce



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. 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:


reduce


Optional


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. Furthermore it is also not short-circuiting.


BinaryOperator






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

ャフサォクコ ケウ,コ,ワ メ,ロスョノ゙,クネ,フムカヤヲニ,エコ゚ツ ウイオン゙ケワサネォキモュキォウイノンコチ゚メヌナイゥフュ,カヒウネェ ネ,ホノケ,ムュキ ッボーミュハ,チ ツス ィ メウイマヤ,゙ウチ ヅ ロ,ォジヌェ ャヌット ェ,マャ,チナエヒネソキツテ トホヲヲミーァ

How do I collapse sections of code in Visual Studio Code for Windows?