Java 8でインデックスを使用してストリームを反復する簡潔な方法はありますか?


382

ストリーム内のインデックスにアクセスしながら、ストリームを反復する簡潔な方法はありますか?

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};

List<String> nameList;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
        .filter(e -> e.getValue().length() <= e.getKey())
        .map(Entry::getValue)
        .collect(toList());

そこに与えられたLINQの例と比べてかなりがっかりしたようです

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();

より簡潔な方法はありますか?

さらに、zipが移動または削除されたようです...


2
なにintRange()?これまで、Java 8でこのメソッドに遭遇したことはありません。
Rohit Jain 2013

@RohitJainおそらくIntStream.rangeClosed(x, y)
アッシリア2013

2
副次的なコメントとして、チャレンジ4は次のように表示されます(IMO)List<String> allCities = map.values().stream().flatMap(list -> list.stream()).collect(Collectors.toList());
アッシリア

3
はい、またはzipと呼ばれる実験的な2値ストリームとともに削除されました。主な問題は、これを効果的に行うには、Javaが構造的に型付けされたペア(またはタプル)の型を実際に必要とすることです。欠けているのは、汎用のPairクラスまたはTupleクラスを作成するのが簡単なことです。これは何度も行われていますが、すべて同じタイプに消去されます。BiStreamMapStream
Stuart Marks、2013

3
ああ、ジェネリックPairまたはTupleクラスのもう1つの問題は、すべてのプリミティブをボックス化する必要があることです。
スチュアートマークス

回答:


435

最もクリーンな方法は、インデックスのストリームから開始することです。

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
         .filter(i -> names[i].length() <= i)
         .mapToObj(i -> names[i])
         .collect(Collectors.toList());

結果のリストには「Erik」のみが含まれます。


forループに慣れ親しんでいると思われる代替案の1つは、次のような可変オブジェクトを使用してアドホックカウンターを維持することAtomicIntegerです。

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
                          .filter(n -> n.length() <= index.incrementAndGet())
                          .collect(Collectors.toList());

並列ストリームで後者のメソッド使用すると、アイテムが「順番に」処理される必要がないため、機能しなくなる可能性があることに注意しください。


28
この方法でアトミックを使用すると、並列ストリームで問題が発生します。まず、要素の処理の順序は、要素が最初の配列で発生する順序と必ずしも同じではありません。したがって、アトミックを使用して割り当てられた「インデックス」は、実際の配列のインデックスと一致しない可能性があります。次に、アトミックはスレッドセーフですが、アトミックを更新する複数のスレッド間で競合が発生し、並列処理の量が低下する可能性があります。
スチュアートマークス

1
@assyliasのようなソリューションを開発しました。上記の並列ストリーム@StuartMarksの問題を回避するために、最初に特定の並列ストリームをシーケンシャルにし、マッピングを実行して並列状態を復元します。 public static <T> Stream<Tuple2<Integer, T>> zipWithIndex(Stream<T> stream) { final AtomicInteger index = new AtomicInteger(); final Function<T, Tuple2<Integer, T>> zipper = e -> Tuples.of(index.getAndIncrement(), e); if (stream.isParallel()) { return stream.sequential().map(zipper).parallel(); } else { return stream.map(zipper); } }
Daniel Dietrich

4
@DanielDietrich質問を解決すると思われる場合は、コメントではなく回答として投稿してください(コードも読みやすくなります)。
アッシリア14

3
@DanielDietrich申し訳ありませんが、そのコードを正しく読んでいると、機能しません。パイプラインの異なるセグメントを並列と順次で実行することはできません。端末操作が開始されると、最後のparallelまたはのみsequentialが受け入れられます。
スチュアートマークス

4
正義のために、@ Stuartの答えから「最もきれいな方法」が盗まれました。
Vadzim 2015

70

Java 8ストリームAPIには、ストリーム要素のインデックスを取得する機能や、ストリームをまとめて圧縮する機能がありません。特定のアプリケーション(LINQの課題など)が他のアプリケーションよりも困難になるため、これは残念です。

ただし、多くの場合、回避策があります。通常、これは、整数の範囲でストリームを「駆動」し、元の要素が配列またはインデックスでアクセス可能なコレクションにあることが多いという事実を利用して行うことができます。たとえば、チャレンジ2の問題は次の方法で解決できます。

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};

List<String> nameList =
    IntStream.range(0, names.length)
        .filter(i -> names[i].length() <= i)
        .mapToObj(i -> names[i])
        .collect(toList());

上で述べたように、これはデータソース(names配列)が直接インデックス付けできるという事実を利用しています。そうでなければ、この手法は機能しません。

これは課題2の意図を満たしていないことを認めます。それでも、問題を合理的に効果的に解決します。

編集

前のコード例flatMapは、フィルター操作とマップ操作を融合するために使用されましたが、これは扱いにくく、利点がありませんでした。Holgerからのコメントに従って例を更新しました。


7
いかがIntStream.range(0, names.length).filter(i->names[i].length()<=i).mapToObj(i->names[i])ですか?それはボクシングなしで機能します…
Holger

1
ええと、なぜ私はflatMapとにかく使用する必要があると思いましたか?
スチュアートマークス

2
最後にこれを再検討します... flatMapフィルタリングとマッピング操作を単一の操作に分類するため、おそらく使用しましたが、これは実際には利点がありません。例を編集します。
スチュアートマークス

Stream.of(Array)は、配列のストリームインターフェイスを作成します。効果的にStream.of( names ).filter( n -> n.length() <= 1).collect( Collectors.toList() );ボックス化を解除し、メモリ割り当てを減らします。レンジストリームをもう作成していないので。
Code Eyez

44

グアバ21以来、あなたは使うことができます

Streams.mapWithIndex()

例(公式ドキュメントから):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")

3
また、グアバの人々はまだforEachWithIndex(関数ではなくコンシューマーを取得)を実装していませんが、割り当てられた問題です:github.com/google/guava/issues/2913
ジョングラスマイヤー

25

プロジェクトで次のソリューションを使用しました。可変オブジェクトや整数範囲を使用するよりも良いと思います。

import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;


public class CollectionUtils {
    private CollectionUtils() { }

    /**
     * Converts an {@link java.util.Iterator} to {@link java.util.stream.Stream}.
     */
    public static <T> Stream<T> iterate(Iterator<? extends T> iterator) {
        int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE;
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false);
    }

    /**
     * Zips the specified stream with its indices.
     */
    public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) {
        return iterate(new Iterator<Map.Entry<Integer, T>>() {
            private final Iterator<? extends T> streamIterator = stream.iterator();
            private int index = 0;

            @Override
            public boolean hasNext() {
                return streamIterator.hasNext();
            }

            @Override
            public Map.Entry<Integer, T> next() {
                return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next());
            }
        });
    }

    /**
     * Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream.
     * The first argument of the function is the element index and the second one - the element value. 
     */
    public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) {
        return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue()));
    }

    public static void main(String[] args) {
        String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};

        System.out.println("Test zipWithIndex");
        zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry));

        System.out.println();
        System.out.println("Test mapWithIndex");
        mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s));
    }
}

+1- StreamSupport.stream()カスタムイテレータを使用して、Nインデックスごとに要素を「挿入」する関数を実装できました。
2014

13

プロトンパックに加えて、jOOλのSeqはこの機能を提供します(そして、cyclops-reactのようにそれを基に構築された拡張ライブラリによって、私はこのライブラリの作者です)。

Seq.seq(Stream.of(names)).zipWithIndex()
                         .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                         .toList();

SeqはSeq.of(names)もサポートし、内部でJDKストリームを構築します。

simple-reactと同等のものは同様に見えます

 LazyFutureStream.of(names)
                 .zipWithIndex()
                 .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                 .toList();

simple-reactバージョンは、非同期/並行処理用にさらに調整されています。


ジョン、今日あなたのライブラリを見ました。私は驚いて混乱しています。
GOXR3PLUS

12

完全を期すために、StreamExライブラリに関連するソリューションを次に示します。

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
    .filterKeyValue((idx, str) -> str.length() <= idx+1)
    .values().toList();

ここでは、またはのような特定の操作EntryStream<Integer, String>を拡張Stream<Entry<Integer, String>>および追加するを作成します。また、ショートカットが使用されています。filterKeyValuevaluestoList()


すごい仕事; のショートカットはあり.forEach(entry -> {}) ますか?
Steve Oh

2
@SteveOh私があなたの質問を正しく理解すれば、はい、あなたは書くことができます.forKeyValue((key, value) -> {})
Tagir Valeev

8

ストリームがリストまたは配列で作成されたときに、ここで解決策を見つけました(そしてサイズを知っています)。しかし、ストリームのサイズが不明な場合はどうなりますか?この場合、このバリアントを試してください:

public class WithIndex<T> {
    private int index;
    private T value;

    WithIndex(int index, T value) {
        this.index = index;
        this.value = value;
    }

    public int index() {
        return index;
    }

    public T value() {
        return value;
    }

    @Override
    public String toString() {
        return value + "(" + index + ")";
    }

    public static <T> Function<T, WithIndex<T>> indexed() {
        return new Function<T, WithIndex<T>>() {
            int index = 0;
            @Override
            public WithIndex<T> apply(T t) {
                return new WithIndex<>(index++, t);
            }
        };
    }
}

使用法:

public static void main(String[] args) {
    Stream<String> stream = Stream.of("a", "b", "c", "d", "e");
    stream.map(WithIndex.indexed()).forEachOrdered(e -> {
        System.out.println(e.index() + " -> " + e.value());
    });
}

6

あなたが試すことができるリストで

List<String> strings = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Fourth", "Fifth")); // An example list of Strings
strings.stream() // Turn the list into a Stream
    .collect(HashMap::new, (h, o) -> h.put(h.size(), o), (h, o) -> {}) // Create a map of the index to the object
        .forEach((i, o) -> { // Now we can use a BiConsumer forEach!
            System.out.println(String.format("%d => %s", i, o));
        });

出力:

0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth

1
実際には素晴らしいアイデアですが、strings :: indexOfは少し高くつくかもしれません。私の提案は代わりに使用することです:.collect(HashMap :: new、(h、s)-> h.put(h.size()、s)、(h、s)-> {})。単純にsize()メソッドを使用してインデックスを作成できます。
gil.fernandes 2017年

@ gil.fernandes提案ありがとうございます。編集を行います。
V0idst4r 2017年

3

Streama Streamは他とは異なるため、インデックスにアクセスしながら反復処理する方法はありませんCollection。A Streamは、ドキュメントに記載されているように、データをある場所から別の場所に運ぶためのパイプラインにすぎません。

ストレージなし。ストリームは、要素を格納するデータ構造ではありません。代わりに、それらは計算操作のパイプラインを介してソース(データ構造、ジェネレーター、IOチャネルなど)から値を運ぶ。

もちろん、あなたは常にあなたを変換することができ、あなたの質問にほのめかしているように見えるStream<V>までCollection<V>のような、List<V>ここであなたがインデックスにアクセスする必要があります。


2
これは他の言語/ツールで利用できます。これは単にマップ関数に渡される増分値です
Lee Campbell

ドキュメントへのリンクが壊れています。
ウスマンムタワキル2018

3

https://github.com/poetix/protonpack uがそのジッパーを行うことができます。

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};

List<String> nameList;
Stream<Integer> indices = IntStream.range(0, names.length).boxed(); 

nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new)
        .filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());                   

System.out.println(nameList);

3

サードパーティのライブラリを使用してもかまわない場合は、Eclipseコレクションが用意されておりzipWithIndexforEachWithIndexさまざまなタイプで使用できます。これは、JDKタイプとを使用したEclipseコレクションタイプの両方に対するこの課題に対する一連のソリューションzipWithIndexです。

String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
ImmutableList<String> expected = Lists.immutable.with("Erik");
Predicate<Pair<String, Integer>> predicate =
    pair -> pair.getOne().length() <= pair.getTwo() + 1;

// JDK Types
List<String> strings1 = ArrayIterate.zipWithIndex(names)
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings1);

List<String> list = Arrays.asList(names);
List<String> strings2 = ListAdapter.adapt(list)
    .zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings2);

// Eclipse Collections types
MutableList<String> mutableNames = Lists.mutable.with(names);
MutableList<String> strings3 = mutableNames.zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings3);

ImmutableList<String> immutableNames = Lists.immutable.with(names);
ImmutableList<String> strings4 = immutableNames.zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings4);

MutableList<String> strings5 = mutableNames.asLazy()
    .zipWithIndex()
    .collectIf(predicate, Pair::getOne, Lists.mutable.empty());
Assert.assertEquals(expected, strings5);

forEachWithIndex代わりに使用する解決策は次のとおりです。

MutableList<String> mutableNames =
    Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik");
ImmutableList<String> expected = Lists.immutable.with("Erik");

List<String> actual = Lists.mutable.empty();
mutableNames.forEachWithIndex((name, index) -> {
        if (name.length() <= index + 1)
            actual.add(name);
    });
Assert.assertEquals(expected, actual);

上記のラムダを匿名の内部クラスに変更すると、これらのコード例はすべてJava 5〜7でも機能します。

注:私はEclipseコレクションのコミッターです


2

Vavr(旧称Javaslang)を使用している場合は、専用のメソッドを利用できます。

Stream.of("A", "B", "C")
  .zipWithIndex();

コンテンツを印刷すると、興味深いものが表示されます。

Stream((A, 0), ?)

これはStreams怠惰であり、ストリーム内の次のアイテムについての手がかりがないためです。


1

述語に基づいてインデックスを取得しようとしている場合は、これを試してください:

最初のインデックスのみを気にする場合:

OptionalInt index = IntStream.range(0, list.size())
    .filter(i -> list.get(i) == 3)
    .findFirst();

または、複数のインデックスを検索する場合:

IntStream.range(0, list.size())
   .filter(i -> list.get(i) == 3)
   .collect(Collectors.toList());

.orElse(-1);見つからない場合に値を返したい場合に備えて追加します。


1

ここにAbacusUtilによるコードがあります

Stream.of(names).indexed()
      .filter(e -> e.value().length() <= e.index())
      .map(Indexed::value).toList();

情報開示:私はAbacusUtilの開発者です。


1

IntStream.iterate()インデックスを取得するために使用できます。

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i < names.length, i -> i + 1)
        .filter(i -> names[i].length() <= i)
        .mapToObj(i -> names[i])
        .collect(Collectors.toList());

これは、Java 8以降のJava 9以降でのみ機能します。これを使用できます。

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i + 1)
        .limit(names.length)
        .filter(i -> names[i].length() <= i)
        .mapToObj(i -> names[i])
        .collect(Collectors.toList());

0

以下の例で必要なように、静的内部クラスを作成してインデクサーをカプセル化できます。

static class Indexer {
    int i = 0;
}

public static String getRegex() {
    EnumSet<MeasureUnit> range = EnumSet.allOf(MeasureUnit.class);
    StringBuilder sb = new StringBuilder();
    Indexer indexer = new Indexer();
    range.stream().forEach(
            measureUnit -> {
                sb.append(measureUnit.acronym);
                if (indexer.i < range.size() - 1)
                    sb.append("|");

                indexer.i++;
            }
    );
    return sb.toString();
}

0

この質問(booleanに一致する最初の要素のインデックスを取得するためのStream Way)は、現在の質問を重複としてマークしているため、ここでは回答できません。ここで答えます。

以下は、外部ライブラリを必要としない一致するインデックスを取得するための一般的なソリューションです。

リストがあれば。

public static <T> int indexOf(List<T> items, Predicate<T> matches) {
        return IntStream.range(0, items.size())
                .filter(index -> matches.test(items.get(index)))
                .findFirst().orElse(-1);
}

次のように呼び出します。

int index = indexOf(myList, item->item.getId()==100);

そして、コレクションを使用する場合は、これを試してください。

   public static <T> int indexOf(Collection<T> items, Predicate<T> matches) {
        int index = -1;
        Iterator<T> it = items.iterator();
        while (it.hasNext()) {
            index++;
            if (matches.test(it.next())) {
                return index;
            }
        }
        return -1;
    }

0

1つの可能な方法は、フローの各要素にインデックスを付けることです。

AtomicInteger index = new AtomicInteger();
Stream.of(names)
  .map(e->new Object() { String n=e; public i=index.getAndIncrement(); })
  .filter(o->o.n.length()<=o.i) // or do whatever you want with pairs...
  .forEach(o->System.out.println("idx:"+o.i+" nam:"+o.n));

ストリームに沿って匿名クラスを使用することは、あまり有用ではありませんが、あまり使用されていません。


0

map 必ずしも
それがLINQの例に最も近いラムダである必要はありません

int[] idx = new int[] { 0 };
Stream.of( names ).filter( name -> name.length() <= idx[0]++ ).collect( Collectors.toList() );

0
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
String completeString
         =  IntStream.range(0,namesArray.length)
           .mapToObj(i -> namesArray[i]) // Converting each array element into Object
           .map(String::valueOf) // Converting object to String again
           .collect(Collectors.joining(",")); // getting a Concat String of all values
        System.out.println(completeString);

出力:サム、パメラ、デイブ、パスカル、エリック

String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};

IntStream.range(0,namesArray.length)
               .mapToObj(i -> namesArray[i]) // Converting each array element into Object
               .map(String::valueOf) // Converting object to String again
               .forEach(s -> {
                //You can do various operation on each element here
                System.out.println(s);
               }); // getting a Concat String of all 

リストに収集するには:

String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
 List<String> namesList
                =  IntStream.range(0,namesArray.length)
                .mapToObj(i -> namesArray[i]) // Converting each array element into Object
                .map(String::valueOf) // Converting object to String again
                .collect(Collectors.toList()); // collecting elements in List
        System.out.println(listWithIndex);

上記の質問の解決策は、List1つの要素を含むErikであると予想されます。
カプラン

リストに収集する例も追加しました。
アルパンサイニ

0

jean-baptiste-yunèsが言ったように、ストリームがJavaリストに基づいている場合、AtomicIntegerとそのincrementAndGetメソッドを使用することは問題に対する非常に優れた解決策であり、返される整数は元のリストのインデックスに対応します並列ストリームを使用しないでください。


0

forEachでインデックスが必要な場合、これは方法を提供します。

  public class IndexedValue {

    private final int    index;
    private final Object value;

    public IndexedValue(final int index, final Object value) { 
        this.index = index;
        this.value = value;
    }

    public int getIndex() {
        return index;
    }

    public Object getValue() {
        return value;
    }
}

次に、次のように使用します。

@Test
public void withIndex() {
    final List<String> list = Arrays.asList("a", "b");
    IntStream.range(0, list.size())
             .mapToObj(index -> new IndexedValue(index, list.get(index)))
             .forEach(indexValue -> {
                 System.out.println(String.format("%d, %s",
                                                  indexValue.getIndex(),
                                                  indexValue.getValue().toString()));
             });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.