web-dev-qa-db-ja.com

Java 8ラムダを使用してストリームからアイテムの範囲を取得する方法は?

前の質問で[ Java 8で動的にフィルタリングを行う方法? ] Stuart Marksは素晴らしい答えを提供し、ストリームからtopNとtopPercentの選択を処理するいくつかの便利なユーティリティを提供しました。

彼の元の答えからここにそれらを含めます:

@FunctionalInterface
public interface Criterion {
    Stream<Widget> apply(Stream<Widget> s);
}

Criterion topN(Comparator<Widget> cmp, long n) {
    return stream -> stream.sorted(cmp).limit(n);
}

Criterion topPercent(Comparator<Widget> cmp, double pct) {
    return stream -> {
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .limit((long)(temp.size() * pct));
    };
}

私の質問は次のとおりです。

[1]一定量のアイテムを持つストリームから3〜7の上位アイテムを取得する方法。したがって、ストリームにA1、A2 .. A10のアイテムがある場合、への呼び出し

topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L)

{A3、A4、A5、A6、A7}を返します

私が考えることができる最も簡単な方法は、オリジナルから上位7 [T7]を取得し、オリジナルから上位3 [T3]を取得してから、T7-T3を取得することです。

[2]一定量のアイテムを含むストリームから上位10%から上位30%の上位アイテムを取得する方法。したがって、ストリームにX1、X2 .. X100のアイテムがある場合、

topPercentFromRange(Comparator<Widget> cmp, double from, double to) = topNFromRange(comparing(Widget::length), 0.10, 0.30)

{X10、X11、X12、...、X29、X30}を返します

私が考えることができる最も簡単な方法は、オリジナルから上位30%[TP30]を取得し、オリジナルから上位10%[TP10]を取得してから、TP30-TP10を取得することです。

Java 8 Lambdaを使用して上記の状況を簡潔に表現する良い方法は何ですか?

55
Frank

ユーザーskiwi 回答済み 質問の最初の部分。 2番目の部分は次のとおりです。

(2)一定量のアイテムを含むストリームから上位10%から上位30%の上位アイテムを取得する方法....

これを行うには、他の質問の answertopPercentと同様の手法を使用する必要があります。つまり、おそらくアップストリームフィルタリングが行われた後に、要素の数を取得できるようにするには、要素をリストに収集する必要があります。

カウントを取得したら、必要なカウントとパーセンテージに基づいて、skipおよびlimitの正しい値を計算します。このような何かが動作する可能性があります:

Criterion topPercentFromRange(Comparator<Widget> cmp, double from, double to) {
    return stream -> {
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .skip((long)(temp.size() * from))
                   .limit((long)(temp.size() * (to - from)));
    };
}

もちろん、fromtoでエラーチェックを行う必要があります。より微妙な問題は、放出する要素の数を決定することです。たとえば、10個の要素がある場合、それらは0%、10%、20%、...、90%に対応するインデックス[0..9]にあります。しかし、9%から11%の範囲を要求する場合、上記のコードは要素をまったく出力せず、予想される10%の要素は出力しません。そのため、あなたがやろうとしていることのセマンティクスに適合するために、おそらくパーセンテージ計算をいじる必要があります。

39
Stuart Marks

Stream<T>から範囲を取得するには、skip(long n)を使用して最初に一定数の要素をスキップし、次にlimit(long n)を呼び出して特定の量のアイテムのみを取得できます。

10個の要素を持つストリームを考えて、要素3〜7を取得するには、通常Listから呼び出します。

list.subList(3, 7);

ここでStreamを使用すると、最初に3つのアイテムをスキップしてから、7-3 = 4のアイテムを取得する必要があるため、次のようになります。

stream.skip(3).limit(4);

2番目の答えに対する@StuartMarksのソリューションの変形として、チェーンを完全に残す可能性を残す次のソリューションを提供します。@ StuartMarksが行う方法と同様に機能します。

private <T> Collector<T, ?, Stream<T>> topPercentFromRangeCollector(Comparator<T> comparator, double from, double to) {
    return Collectors.collectingAndThen(
        Collectors.toList(),
        list -> list.stream()
            .sorted(comparator)
            .skip((long)(list.size() * from))
            .limit((long)(list.size() * (to - from)))
    );
}

そして

IntStream.range(0, 100)
        .boxed()
        .collect(topPercentFromRangeCollector(Comparator.comparingInt(i -> i), 0.1d, 0.3d))
        .forEach(System.out::println);

これにより、要素10〜29が印刷されます。

ストリームから要素を取り込んでCollector<T, ?, Stream<T>>に変換するList<T>を使用することで機能し、Stream<T>を取得し、並べ替えて(正しい)境界を適用します。

45
skiwi