web-dev-qa-db-ja.com

Java 8リスト内の要素を見つけるためのストリーム

次のクラスがあります。

public class Item {
    int id;
    String name;
    // few other fields, contructor, getters and setters
}

アイテムのリストがあります。リストを反復処理して、特定のIDを持つインスタンスを見つけたいです。私はストリームを介してそれをやろうとしています。

public void foobar() {

    List<Item> items = getItemList();
    List<Integer> ids = getIdsToLookup();
    int id, i = ids.size() - 1;

    while (i >= 0) {
        id = ids.get(i);
        Optional<Item> item = items
            .stream()
            .filter(a -> a.getId() == id)
            .findFirst();
        // do stuff
        i--;
    }
}

これはリストを反復処理し、必要な要素を取得する最良の方法ですか?また、ラムダ式で使用される変数が最終または実質的に最終でなければならないというidのフィルター行でエラーが発生します。たぶん、whileループ内でidを定義して、例外を取り除くことができます。ありがとう。

9
Gengis Khan

検索するIDがたくさんある場合は、各IDに対して線形検索を行うのではなく、単一のパスでそれを行うソリューションを使用することをお勧めします。

_Map<Integer,Optional<Item>> map=ids.stream()
    .collect(Collectors.toMap(id -> id, id -> Optional.empty()));
items.forEach(item ->
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item)));
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) {
    map.get(it.previous()).ifPresent(item -> {
        // do stuff
    });
}
_

最初のステートメントは、idsリストからマップを作成し、各検索IDを空のOptionalにマッピングします。

2番目のステートメントは、forEachを使用してアイテムを反復処理し、各アイテムについて、そのidから空のOptionalへのマッピングがあるかどうかを確認し、そのようなマッピングがある場合はすべてを1つの操作でカプセル化するOptionalに置き換えます。 computeIfPresent

最後のforループは、idsリストを逆順に繰り返します。これは、それらをその順序で処理し、空でないOptionalがある場合にアクションを実行したいためです。マップはリストにあるすべてのIDで初期化されているため、getnullを返すことはありません。IDがOptionalリストにない場合、空のitemsを返します。

そのようにして、MapのルックアップにO(1)時間の複雑さがあると仮定すると(これは一般的な実装の場合です)、ネット時間の複雑さはO(m×n)からO(m+n)...

8
Holger

次のようなものを使用してみてください:

ids.forEach(id -> 
    list.stream()
    .filter(p -> p.getId() == id)
    .findFirst()
    .ifPresent(p -> {
        // do stuff here
    });
);

ここでオプションは、フィルターメソッドが空のストリームを返すことができることを示しています。

10
ByeBye

ストリームに固執して逆方向に反復したい場合は、次のようにできます。

_IntStream.iterate(ids.size() - 1, i -> i - 1)
    .limit(ids.size())
    .map(ids::get) // or .map(i -> ids.get(i))
    .forEach(id -> items.stream()
        .filter(item -> item.getId() == id)
        .findFirst().ifPresent(item -> {
            // do stuff
        }));
_

このコードはあなたのものと同じです。

シードids.size() - 1で始まり、逆方向に反復します。 intsの初期ストリームのサイズはlimit()で制限されているため、負のintsはなく、ストリームはids。次に、map()操作がインデックスをidリストのi番目の位置にある実際のidsに変換します(これはids.get(i))。最後に、コードと同じ方法で、アイテムがitemsリストで検索されます。

特定のIDごとに最大1つのアイテムを検索し、見つかったアイテムに対して何かを実行しますか?もう少しパフォーマンスが向上します。

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set

items.stream()
    .filter(e -> idsToLookup.remove(e.getId()))
    .forEach(
       /* doing something */
     );
0
user_3380739