I have to write a method that takes a SortedSet<MyEvent>
and a List<String>
. It has to determine if there is a successive sequence of MyEvent
representing the given List<String>
by a certain class attribute.
Let's assume there was the following (code-)situation:
List<String> list = new ArrayList<String>()
list.add("AAA");
list.add("BBB");
and a Set<MyEvent>
SortedSet<MyEvent> events = new TreeSet<MyEvent>();
with objects of type MyEvent
which implements Comparable<MyEvent>
(comparison by LocalDateTime
only).
The given List<String>
represents a sequence of abbreviations and I need to find the most recent occurrence of a sequence of MyEvent
s whose class attributes abbreviation
have the values of the sequence.
This is what I have done so far:
public static void main(String[] args) {
SortedSet<MyEvent> events = generateSomeElements();
List<String> sequence = new ArrayList<>();
sequence.add("AAA");
sequence.add("BBB");
MyEvent desired = getMostRecentLastEventOfSequence(events, sequence);
System.out.println("Result: " + desired.toString());
}
public static MyEvent getMostRecentLastEventOfSequence(SortedSet<MyEvent> events,
List<String> sequence) {
// "convert" the events to a List in order to be able to access indexes
List<MyEvent> myEvents = new ArrayList<MyEvent>();
events.forEach(event -> myEvents.add(event));
// provide a temporary data structure for possible results
SortedSet<MyEvent> possibleReturnValues = new TreeSet<MyEvent>();
// iterate the events in order to find those with a specified predecessor
for (int i = 0; i < myEvents.size(); i++) {
if (i > 0) {
// consider only successive elements
MyEvent a = myEvents.get(i - 1);
MyEvent b = myEvents.get(i);
// check if there is a
if (a.getAbbreviation().equals(sequence.get(0))
&& b.getAbbreviation().equals(sequence.get(1))) {
// if a sequence was found, add the last element to the possible results
possibleReturnValues.add(b);
}
}
}
// check if there were possible results
if (possibleReturnValues.size() == 0) {
return null;
} else {
// if there are any, return the most recent / latest one
return possibleReturnValues.stream().max(MyEvent::compareTo).orElse(null);
}
}
The method is working (for this 2-element sequence, at least).
Is it possible to do that in a single call using the stream API (and for an unknown size of the sequence)?
Your task is not so hard, just create a Stream
, apply a filter
, and ask for the maximum value. There is the the obstacle that we need a previous element in the predicate, but we have hands on the source collection, which can provide it.
In practice, every SortedSet
is also a NavigableSet
which provides a lower
method to get the previous element, if there is one, but since your requirement is to support a SortedSet
input, we have to provide a fall-back for the theoretical case of a SortedSet
not being a NavigableSet
.
Then, the operation can be implemented as
public static MyEvent getMostRecentLastEventOfSequence(
SortedSet<MyEvent> events, List<String> sequence) {
String first = sequence.get(0), second = sequence.get(1);
UnaryOperator<MyEvent> previous;
if (events instanceof NavigableSet) {
NavigableSet<MyEvent> navigableSet = (NavigableSet<MyEvent>) events;
previous = navigableSet::lower;
}
else previous = event -> events.headSet(event).last();
return events.stream()
.filter(event -> event.getAbbreviation().equals(second))
.filter(event -> {
MyEvent p = previous.apply(event);
return p != null && p.getAbbreviation().equals(first);
})
.max(Comparator.naturalOrder()).orElse(null);
}
but we can do better than that. Since we now we are searching for a maximum in a sorted input, we know that the first match is sufficient when iterating backwards. Again, it is much smoother when the input is actually a NavigableSet
:
public static MyEvent getMostRecentLastEventOfSequence(
SortedSet<MyEvent> events, List<String> sequence) {
String first = sequence.get(0), second = sequence.get(1);
UnaryOperator<MyEvent> previous;
Stream<MyEvent> stream;
if (events instanceof NavigableSet) {
NavigableSet<MyEvent> navigableSet = (NavigableSet<MyEvent>) events;
previous = navigableSet::lower;
stream = navigableSet.descendingSet().stream();
}
else {
previous = event -> events.headSet(event).last();
stream = Stream.iterate(events.last(), previous).limit(events.size());
}
return stream
.filter(event -> event.getAbbreviation().equals(second))
.filter(event -> {
MyEvent p = previous.apply(event);
return p != null && p.getAbbreviation().equals(first);
})
.findFirst().orElse(null);
}
So this method will search backwards and stop at the first match, which will already be the maximum element, without the need to traverse all elements.