ワンライナーでストリーム/リストの最後の要素を取得する


118

次のコードでストリームまたはリストの最後の要素を取得するにはどうすればよいですか?

どこdata.careasList<CArea>

CArea first = data.careas.stream()
                  .filter(c -> c.bbox.orientationHorizontal).findFirst().get();

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList()).; //how to?

ご覧のとおり、最初の要素を特定filterので取得することは難しくありません。

ただし、ワンライナーの最後の要素を取得するのは本当に大変です。

  • から直接入手できないようStreamです。(それは有限のストリームに対してのみ意味があります)
  • また、インターフェイスのようなものを取得できないようfirst()で、これは本当に苦痛です。last()List

インターフェース内にfirst()last()メソッドを提供しないことについてはList、そこにある要素が順序付けられており、サイズがわかっているため、私は何の議論も見ていません。

しかし、元の答えのとおり:有限の最後の要素を取得する方法はStream

個人的に、これは私が得ることができる最も近いものです:

int lastIndex = data.careas.stream()
        .filter(c -> c.bbox.orientationHorizontal)
        .mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);

ただしindexOf、すべての要素でon を使用する必要があります。これは、パフォーマンスを損なう可能性があるため、通常は必要ない可能性があります。


10
GuavaはIterables.getLastIterableを提供しますが、で動作するように最適化されていListます。ペットのうわさがありませんgetFirstStream一般的に、APIは、便利なメソッドの多くを省略し、恐ろしく肛門です。C#のLINQは、無限の列挙型もサポートしていますが、対照的に、喜んで提供.Last().Last(Func<T,Boolean> predicate)ます。
Aleksandr Dubinsky、2014

@AleksandrDubinskyは賛成票を投じましたが、読者への1つの注記です。両者は非常に異なるパラダイムで行わStreamれるLINQため、APIは完全に比較することはできません。良くも悪くもないだけです。そして、間違いなくいくつかの方法が欠落しているのは、Oracle
開発

1
真のワンライナーの場合、このスレッド役に立つかもしれません。
クォンタム

回答:


185

Stream :: reduceメソッドを使用して最後の要素を取得することができます。次のリストには、一般的なケースの最小限の例が含まれています。

Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);

この実装は、すべての順序付けされたストリームListsから作成されたストリームを含む)で機能します。順不同ストリームには、要素が返されます、不特定の明白な理由のためです。

実装は、シーケンシャルストリームパラレルストリームの両方で機能します。一見するとそれは意外なことかもしれませんが、残念ながらドキュメントには明示的に記載されていません。ただし、これはストリームの重要な機能であり、明確にするようにしています。

  • メソッドのJavadoc ストリームは::減らすことがあること、状態を「されていない実行するために制約順次
  • Javadocはまた、その必要とする「アキュムレータ機能がなければならない会合非干渉ステートレス二つの値合成する機能」明らかラムダ式の場合です、(first, second) -> second
  • Javadoc 縮小オペレーション状態:「クラスは一般的な還元操作の複数の形態を有するストリームと呼ばれる(低減))(コレクト [..]」「適切減らす構築動作である本質的に並列化、限り機能(複数可)要素の処理に使用されるのは、関連性があり、ステートレスです。」

密接に関連するコレクターのドキュメントはさらに明確です。シーケンシャル実行パラレル実行で同等の結果が生成されることを保証するには、コレクター関数がIDと結合規則の制約を満たす必要があります。」


元の質問に戻ります。次のコードは、変数の最後の要素への参照を格納しlast、ストリームが空の場合は例外をスローします。複雑さはストリームの長さに比例します。

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();

いいね、ありがとう!ちなみに_、パラメータが必要ない場合に、名前を省略する可能性があるかどうか(おそらくを使用するなど)を知っていますか?つまり.reduce((_, current) -> current)、そのawsが有効な構文である場合のみです。
skiwi、2014年

2
:@skiwiあなたはどんな法的な変数名、例えば使用することができます.reduce(($, current) -> current).reduce((__, current) -> current)(二重のアンダースコアを)。
アサイリア2014年

2
技術的には、どのストリームでも機能しない可能性があります。あなたが参照するドキュメントは、エンカウンター順序に従うStream.reduce(BinaryOperator<T>)かどうかについては言及していませんreduce。また、ストリームが順序付けされている場合でも、ターミナル操作はエンカウンター順序を無視できます。余談ですが、「可換性」という言葉はStream javadocsには表示されないため、その不在からはあまりわかりません。
Aleksandr Dubinsky、2014

2
@AleksandrDubinsky:正確には、ドキュメントには可換性についての記述がありません。これは、reduce操作には関係がないためです。重要な部分は次のとおりです。「[..]要素を処理するために使用される関数が結合[..]である限り、適切に構築された削減操作は本質的に並列化可能です。」
nosid 2014

2
@Aleksandr Dubinsky:もちろん、これは「仕様の理論上の問題」ではありません。reduce((a,b)->b)(もちろん、順序付けられたストリームの)最後の要素を取得するための正しいソリューションであるかどうかの違いになります。Brian Goetzの声明はポイントを作り、さらにAPIドキュメントreduce("", String::concat)文字列連結の非効率的ですが正しい解決策であると述べています。これは遭遇順序の維持を意味します。
Holger

42

コレクション(またはより一般的なIterable)がある場合は、Google Guavaの

Iterables.getLast(myIterable)

便利なワンライナーとして。


1
そして、あなたは簡単に反復可能オブジェクトにストリームを変換することができます:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

1つのライナー(ストリームの必要はありません;):

Object lastElement = list.get(list.size()-1);

30
リストが空の場合、このコードはをスローしArrayIndexOutOfBoundsExceptionます。
ドラゴン

8

グアバはこのケースのために専用の方法を持っています:

Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);

それは同等ですがstream.reduce((a, b) -> b)、クリエイターはパフォーマンスがはるかに優れていると主張しています。

ドキュメントから:

このメソッドのランタイムはO(log n)とO(n)の間であり、効率的に分割可能なストリームでより良いパフォーマンスを発揮します。

ストリームが順序付けされていない場合、このメソッドはのように動作することを言及する価値がありfindAny()ます。


1
@ZhekaKozlov一種...ホルジャースはいくつかの欠陥をここに
ユージーン

0

最後のN個の要素を取得する必要がある場合。クロージャーが使用できます。以下のコードは、ストリームが最後に到達するまで、固定サイズの外部キューを維持します。

    final Queue<Integer> queue = new LinkedList<>();
    final int N=5;
    list.stream().peek((z) -> {
        queue.offer(z);
        if (queue.size() > N)
            queue.poll();
    }).count();

別のオプションは、IDをキューとして使用して削減操作を使用することです。

    final int lastN=3;
    Queue<Integer> reduce1 = list.stream()
    .reduce( 
        (Queue<Integer>)new LinkedList<Integer>(), 
        (m, n) -> {
            m.offer(n);
            if (m.size() > lastN)
               m.poll();
            return m;
    }, (m, n) -> m);

    System.out.println("reduce1 = " + reduce1);

-1

以下のようにskip()関数を使用することもできます...

long count = data.careas.count();
CArea last = data.careas.stream().skip(count - 1).findFirst().get();

使い方はとても簡単です。


注:膨大なコレクション(数百万のエントリ)を処理する場合は、ストリームの「スキップ」に依存しないでください。「スキップ」は、N番目の数に達するまですべての要素を反復することによって実装されるためです。それを試してみました。単純なインデックスによる取得操作と比較して、パフォーマンスに非常にがっかりしました。
java.is.for.desktop

1
リストが空の場合もスローされますArrayIndexOutOfBoundsException
JindraVysocký19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.