web-dev-qa-db-ja.com

Java 8+ストリーム:リストが私のオブジェクトインスタンスの2つのフィールドの正しい順序にある​​かどうかを確認します

タイトルは少しあいまいかもしれませんが、私が持っているもの(私有化コード)は次のとおりです。

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

考えられる解決策の1つは、Pairsこのように を作成してから、!および.anyMatchを使用して、個々のPair<MyObj>をチェックすることです。しかし、可能であれば、この目的のためだけにPairクラスを作成したくありません。

MyObjのペアをループしてチェックするために、.reduceまたは何かを使用する方法はありますか? Java 8ストリームを使用して、リスト内のMyObjのすべての日付とパーセンテージが正しい順序になっているかどうかを確認するための最善の方法は何ですか?

もう1つの可能性は、おそらくリストを日付でソートし、それらがすべてパーセントで表示されているかどうかを確認することです。両方のフィールドを同時に確認するよりも簡単です。ただし、MyObjのペアをパーセンテージで比較する場合と同じ問題が残っています。

(追記: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

StreamExpairMapによる解決策は次のとおりです

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を使用して2つのアイテムを比較できます(ただし、これは最良のオプションではありません)。次のように、順序が狂っているアイテムを見つけたら、結果として新しい「空の」アイテムを作成する必要があります。

    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

これはストリームを使って解決すべき問題だとは思いません。ストリームは、コレクションの要素にマッピングとフィルタリングを個別に適用します(おそらく、異なる要素の処理を異なるCPUコアに分散します)。その後、それらを新しいコレクションに収集するか、それらを何らかの累積値に減らします。あなたの問題は、ストリームの目的と矛盾するコレクションの異なる要素間の関係に関係しています。小川を含む解決策があるかもしれませんが、それらはペンチで壁に釘を打ち込むようなものです。古典的なループは完全にうまくいきます:順序を破る要素の最初の発生を見つけて、望ましい結果を返します!したがって、ペアを作成する必要すらありません。

0
Amadán

@luis g。の回答と同様に、reduceOptional(空はソートされていないことを意味します)と組み合わせて使用​​し、IDとして「最小」の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