Найти последовательную последовательность атрибутов String в наборе объектов (с помощью потокового API) - PullRequest
4 голосов
/ 06 мая 2019

Я должен написать метод, который принимает SortedSet<MyEvent> и List<String>.Он должен определить, существует ли последовательная последовательность MyEvent, представляющая данное List<String> определенным атрибутом класса.

Давайте предположим, что была следующая (кодовая) ситуация:

List<String> list = new ArrayList<String>()
list.add("AAA");
list.add("BBB");

и Set<MyEvent>

SortedSet<MyEvent> events = new TreeSet<MyEvent>();

с объектами типа MyEvent, которые implements Comparable<MyEvent> (сравнение только по LocalDateTime).
Указанный List<String> представляет собой последовательность сокращенийи мне нужно найти самое последнее вхождение последовательности MyEvent s, у атрибутов класса abbreviation которой есть значения последовательности.

Это то, что я сделал до сих пор:

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

Метод работает (по крайней мере для этой последовательности из 2 элементов).

Возможно ли это сделать за один вызов с использованием потокового API (и для неизвестного размера последовательности)?

Ответы [ 2 ]

3 голосов
/ 06 мая 2019

Ваша задача не так сложна, просто создайте Stream, примените filter и запросите максимальное значение.Есть препятствие в том, что нам нужен предыдущий элемент в предикате, но у нас есть руки к исходной коллекции, которая может предоставить ее.

На практике каждый SortedSet также является NavigableSet, который обеспечиваетlower метод, чтобы получить предыдущий элемент, если он есть, но так как ваше требование заключается в поддержке ввода SortedSet, мы должны предоставить запасной вариант для теоретического случая SortedSet не являясь NavigableSet.

Тогда операция может быть реализована как

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

, но мы можем добиться большего, чем это.Поскольку теперь мы ищем максимум во входе sorted , мы знаем, что первого совпадения достаточно при итерации в обратном направлении.Опять же, это намного более плавно, когда входные данные на самом деле являются 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);
}

Так что этот метод будет искать в обратном направлении и останавливаться на первом совпадении, которое уже будет максимальным элементом, без необходимостипройти все элементы.

1 голос
/ 06 мая 2019

Другое решение - использование индекса.Размер последовательности не будет ограничен 2.

public static MyEvent getMostRecentLastEventOfSequence(SortedSet<MyEvent> events, List<String> sequence) {
  final List<MyEvent> eventList = new ArrayList<>(events);
  Collections.reverse(eventList);
  final int seqLength = sequence.size();

  OptionalInt first = IntStream.range(0, eventList.size() - seqLength + 1)
      .filter(i -> IntStream.range(0, seqLength)
          .allMatch(j -> eventList.get(i + j).getAbbreviation().equals(sequence.get(seqLength - j - 1))))
      .findFirst();

  return first.isPresent() ? eventList.get(first.getAsInt()) : null;
}

Вот пример тестового кода:

@Test
void test_56005015() throws Exception {
  List<String> sequence = Arrays.asList("AAA", "BBB", "CCC");

  SortedSet<MyEvent> events = new TreeSet<>();
  events.add(new MyEvent("AAA", LocalDateTime.now().plusDays(1)));
  events.add(new MyEvent("BBB", LocalDateTime.now().plusDays(2)));
  events.add(new MyEvent("CCC", LocalDateTime.now().plusDays(3)));
  events.add(new MyEvent("AAA", LocalDateTime.now().plusDays(4)));
  events.add(new MyEvent("BBB", LocalDateTime.now().plusDays(5)));
  events.add(new MyEvent("CCC", LocalDateTime.now().plusDays(6)));

  MyEvent result = getMostRecentLastEventOfSequence(events, sequence);
  System.out.println(result);
}

с классом MyEvent, помеченным Lombok.

@Getter
@Setter
@AllArgsConstructor
@ToString
public static class MyEvent implements Comparable<MyEvent> {
  private String abbreviation;
  private LocalDateTime eventTime;

  @Override
  public int compareTo(MyEvent o) {
    return eventTime.compareTo(o.eventTime);
  }
}
...