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

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

Класс с некоторыми полями, включая 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 лямбда является скорее предпочтением, чем требованием.)

Ответы [ 7 ]

0 голосов
/ 28 августа 2018

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

0 голосов
/ 29 августа 2018

Аналогично ответу @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 голосов
/ 24 августа 2018

Вот решение по 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);
0 голосов
/ 24 августа 2018

Поскольку вы упомянули, что не хотите создавать для этого отдельный класс 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);
0 голосов
/ 24 августа 2018

Ну, если вам нужна операция с коротким замыканием, я не думаю, что существует простое решение с использованием 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));
0 голосов
/ 24 августа 2018

Да, вы можете использовать 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 голосов
/ 24 августа 2018

Я думаю, что вы почти достигли цели, пытаясь использовать 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 для перебора элементов списка с использованием индекса. Таким образом, мы можем ссылаться на любой элемент в списке, например, предыдущий, чтобы сравнить его.

РЕДАКТИРОВАТЬ добавив более общую версию:

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