web-dev-qa-db-ger.com

Java 8+ -Stream: Prüfen Sie, ob die Liste für zwei Felder meiner Objektinstanzen in der richtigen Reihenfolge ist

Der Titel mag ein wenig vage sein, aber hier habe ich (in privatisiertem Code):

Eine Klasse mit einigen Feldern, einschließlich BigDecimal und Date:

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

  // Getters and Setters
}

In einer anderen Klasse habe ich eine Liste dieser Objekte (d. H. Java.util.List<MyObj> myList). Was ich jetzt möchte, ist ein Java 8-Stream, um zu überprüfen, ob die Liste in der richtigen Reihenfolge von Datum und Prozentsatz für meinen Prüfer liegt.

Zum Beispiel wäre die folgende Liste wahrheitsgetreu:

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

Diese Liste wäre jedoch falsch, da der Prozentsatz nicht in der richtigen Reihenfolge liegt:

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

Und diese Liste wäre auch falsch, weil die Daten nicht in der richtigen Reihenfolge sind:

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

Eine mögliche Lösung könnte darin bestehen, Pairswie folgt zu erstellen und dann einen ! und .anyMatch zu verwenden, um jeden einzelnen Pair<MyObj> zu überprüfen. Aber ich möchte nicht wirklich eine Pair-Klasse nur für diesen Zweck erstellen, wenn dies möglich ist.

Gibt es vielleicht eine Möglichkeit, .reduce oder etwas zu verwenden, um Paare von MyObj zu durchlaufen, um sie zu überprüfen? Was wäre der beste Ansatz, um zu überprüfen, ob alle Daten und Prozentsätze der MyObj in meiner Liste in der richtigen Reihenfolge mit Java 8-Stream liegen?

Eine andere Möglichkeit besteht darin, die Liste nach Datum zu sortieren und dann zu prüfen, ob sie alle in der Reihenfolge des Prozentsatzes liegen. Ist das einfacher, als zu prüfen, dass beide Felder gleich sind? Dasselbe Problem besteht beim Vergleich der Paare von MyObj für den Prozentsatz.

(PS: Ich werde es für einen com.vaadin.server.SerializablePredicate<MyObj> validator verwenden, und ich bevorzuge ein Java 8-Lambda, da ich auch einiges für die anderen Validatoren verwendet habe, sodass es eher mit dem Rest des Codes übereinstimmt. Das Java 8-Lambda ist mehr eine Präferenz als Voraussetzung in meiner Frage jedoch.)

17
Kevin Cruijssen

Wenn Sie einen Kurzschlussbetrieb wünschen, glaube ich nicht, dass eine einfache Lösung mit Stream-API besteht ... Ich schlage eine einfachere Lösung vor. Definieren Sie zunächst eine Methode, die Ihnen auf Kurzschlussbasis sagt, ob Ihre Liste ist sortiert oder nicht, basierend auf einigen Parametern:

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

Und es anrufen über:

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

Ich denke, Sie sind fast da, indem Sie versuchen, Stream.anyMatch zu verwenden. Sie können es so erreichen:

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

Wir können IntStream.range verwenden, um die Elemente der Liste mit einem Index zu durchlaufen. Auf diese Weise können wir auf jedes Element in der Liste verweisen, z. das vorherige, um es zu vergleichen.

EDITHinzufügen einer allgemeineren Version:

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

Dies kann mit dem obigen Prädikat wie folgt aufgerufen werden:

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

Oder mit der Methodenreferenz in einer Klasse ListNotOrdered:

isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
4
LuCio

Hier ist die Lösung von pairMap in 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

Da Sie erwähnt haben, dass Sie keine eigene Klasse Pairs erstellen möchten, können Sie eine eingebaute Klasse für diesen Zweck verwenden: AbstractMap.SimpleEntry.

Sie können eine BiPredicate erstellen, die sowohl Ihre Vergleichsbedingungen überprüft als auch alle Paare miteinander vergleicht.

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

Die obige Lösung mit Komparator:

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

Ja, Sie können reduce verwenden, um zwei Elemente zu vergleichen (obwohl dies nicht die beste Option ist). Sie müssen nur einen neuen "leeren" Artikel als Ergebnis erstellen, wenn Sie einen Artikel außerhalb der Reihenfolge finden, wie folgt:

    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 wird nur dann gedruckt, wenn beide Bedingungen erfüllt sind, andernfalls false.

Natürlich können Sie den Code durch Hinzufügen einiger Hilfsmethoden in MyObj noch schöner gestalten:

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

Beachten Sie, dass dies kein Kurzschluss ist, d. H. Es wird die gesamte Liste durchlaufen, selbst wenn ein ungeordnetes Element gefunden wird. Die einzige Möglichkeit, den Stream mit reduce frühzeitig zu verlassen, besteht darin, eine RuntimeException zu werfen, sobald Sie das erste ungeordnete Element gefunden haben. Dennoch wollte ich Ihnen zeigen, dass Sie tatsächlich reduce für diesen Zweck verwenden können.

Sehen Sie sich die Antwort von @ LuCio für einen Kurzschluss an, der beendet wird, sobald er den ersten unpassenden Gegenstand gefunden hat.

0
walen

Ich denke nicht, dass dies ein Problem ist, das mit Streams gelöst werden sollte. Streams wenden Zuordnungen und Filterungen auf die Elemente einer Auflistung unabhängig an (wahrscheinlich sogar, indem sie die Behandlung verschiedener Elemente auf verschiedene CPU-Kerne verteilen), bevor sie erneut in einer neuen Auflistung gesammelt oder auf einen bestimmten Wert reduziert werden. Ihr Problem umfasst Beziehungen zwischen verschiedenen Elementen einer Sammlung, die dem Zweck eines Streams widersprechen. Zwar gibt es Lösungen, die Streams betreffen, aber es wäre, als würde man einen Nagel mit einer Zange in die Wand hämmern. Eine klassische Schleife ist perfekt für Sie: Finden Sie das erste Vorkommen eines Elements, das die Reihenfolge bricht, und geben Sie das gewünschte Ergebnis zurück! Sie müssen also nicht einmal ein Paar erstellen.

0
Amadán

Ähnlich wie bei @luis g. Können Sie auch reduce in Kombination mit Optional (leer bedeutet unsortiert) und einer "minimalen" MyObj als Identität verwenden:

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

Beachten Sie, dass die Akkumulationsfunktion (BinaryOperator) assoziativ sein sollte, was in diesem Fall nicht der Fall ist. Außerdem ist es auch kein Kurzschluss.

0
sfiss