Find a successive sequence of String attributes in a Set of objects (by stream API)

deHaar :

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 MyEvents 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)?

Holger :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=77456&siteId=1