it-roy-ru.com

Поток Java 8+: проверьте, находится ли список в правильном порядке для двух полей моих экземпляров объекта

Название может быть немного расплывчатым, но вот что у меня есть (в приватизированном коде):

Класс с некоторыми полями, включая BigDecimal и Date:

class MyObj{
  private Java.math.BigDecimal percentage;
  private Java.util.Date date;
  // Some more irrelevant fields

  // Getters and Setters
}

В другом классе у меня есть список этих объектов (т.е. Java.util.List<MyObj> myList). Теперь я хочу, чтобы поток Java 8 проверял, находится ли список в правильном порядке и дат, и процентов для моего валидатора.

Например, следующий список будет правдивым:

[ MyObj { percentage = 25, date = 01-01-2018 },
  MyObj { percentage = 50, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

Но этот список будет ошибочным, потому что процент не в правильном порядке:

[ MyObj { percentage = 25, date = 01-01-2018 },
  MyObj { percentage = 20, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

И этот список также будет ошибочным, потому что даты не в правильном порядке:

[ MyObj { percentage = 25, date = 10-03-2018 },
  MyObj { percentage = 50, date = 01-02-2018 },
  MyObj { percentage = 100, date = 15-04-2019 } ]

Одним из возможных решений может быть создание Pairsвот так , а затем использование ! и .anyMatch для проверки каждого отдельного Pair<MyObj>. Но я действительно не хочу создавать класс Pair только для этой цели, если это возможно.

Возможно, есть способ использовать .reduce или что-то такое, чтобы перебирать пары MyObj для их проверки? Каков наилучший подход, чтобы проверить, все ли даты и проценты MyObj в моем списке находятся в правильном порядке, используя поток Java 8?

Другой возможностью, возможно, является сортировка списка по дате, а затем проверка, все ли они в порядке процентов, если это проще, чем проверка того, что оба поля выполняются одновременно. Тем не менее, та же проблема со сравнением пар MyObj для процента все еще остается.

(PS: я буду использовать его для com.vaadin.server.SerializablePredicate<MyObj> validator, и я предпочитаю лямбду Java 8, потому что я также использовал некоторые для других валидаторов, так что это было бы больше в соответствии с остальной частью кода. Лямбда Java 8 больше предпочтение чем требование в моем вопросе однако.)

17
Kevin Cruijssen

Ну, если вам нужна операция с коротким замыканием, я не думаю, что существует простое решение с использованием stream-api ... Я предлагаю более простое, сначала определите метод, который коротким путем скажет вам, если ваш список отсортировано или нет, на основе какого-либо параметра:

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

И позвонив через:

 System.out.println(
          isSorted(myList, MyObj::getPercentage) && 
          isSorted(myList, MyObj::getDate));
17
Eugene

Я думаю, что вы почти там, пытаясь использовать 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) {
    return before.getPercentage().compareTo(after.getPercentage()) > 0 ||
            before.getDate().compareTo(after.getDate()) > 0;
}

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

EDITдобавление более общей версии:

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

Это можно вызвать следующим образом, используя вышеуказанный предикат:

isNotOrderedAccordingTo(myList1, (before, after) -> isNotOrdered(before, after));

Или используя ссылку на метод в классе ListNotOrdered:

isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
4
LuCio

Вот решение от pairMap в StreamEx

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

Поскольку вы упомянули, что для этого вам не нужно создавать отдельный класс Pairs, вы можете использовать встроенный класс для таких целей: AbstractMap.SimpleEntry.

Вы можете создать переменную 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)));

Вышеупомянутое решение с компаратором:

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);
2
Pankaj Singhal

Да, вы можете использовать reduce для сравнения двух элементов (хотя это не лучший вариант). Вам просто нужно создать новый «пустой» элемент в результате, когда вы обнаружите, что элемент не в порядке, например:

    boolean fullyOrdered = myList.stream()
            .reduce((a, b) -> {
                if ((a.percentage == null && a.date == null) || // previous item already failed 
                        a.percentage.compareTo(b.percentage) > 0 || // 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);

Это будет печатать true только если оба ваших условия будут выполнены, false в противном случае.

Конечно, вы можете сделать код лучше, включив некоторые вспомогательные методы в 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 ||
               this.date.after(other.date);
    }
}
// ...
        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);

Имейте в виду, что это не является коротким замыканием, то есть оно будет проходить по всему списку даже после того, как оно найдет неупорядоченный элемент. Единственный способ рано выйти из потока при использовании reduce - это сгенерировать RuntimeException в тот момент, когда вы найдете первый неупорядоченный элемент, и это будет использовать исключения для управления потоком программы, что считается плохой практикой. Тем не менее, я хотел показать вам, что вы действительно можете использовать reduce для этой цели.

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

0
walen

Я не думаю, что это проблема, которую нужно решать с помощью потоков. Потоки применяют сопоставления и фильтрации к элементам коллекции независимо (возможно, даже распределяя обработку различных элементов по разным ядрам ЦП), прежде чем снова собирать их в новую коллекцию или сводить их к некоторому накопленному значению. Ваша проблема включает в себя отношения между различными элементами коллекции, что противоречит цели потока. Хотя могут быть решения с использованием ручьев, это все равно, что вбивать гвоздь в стену плоскогубцами. Классическая петля подойдет вам идеально: найдите первый случай, когда элемент нарушает порядок, и верните желаемый результат! Таким образом, вам даже не нужно создавать пару.

0
Amadán

Подобно ответу @luis g., вы также можете использовать 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();

Обратите внимание, что функция накопления (BinaryOperator) должна быть ассоциативной, чего не происходит в этом случае. Кроме того, это также не короткое замыкание.

0
sfiss