474

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

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();

もっと簡潔な方法はありますか?

さらに、ジッパーが移動または削除されたようです...

4

23 に答える 23

89

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 からのコメントに従って、例を更新しました。

于 2013-09-01T23:31:10.747 に答える
27

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

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));
    }
}
于 2014-04-14T02:58:00.383 に答える
13

protonpack に加えて、jOOλ の Seqはこの機能を提供します (そして、 cyclops-reactのようにビルドされる拡張ライブラリによって、私はこのライブラリの作成者です)。

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

Seq は Seq.of(names) のみをサポートし、内部で JDK Stream を構築します。

同等の単純な反応は同様に次のようになります

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

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

于 2015-03-29T20:25:23.503 に答える
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()

于 2015-09-10T02:07:51.807 に答える
3

サードパーティのライブラリを使用してもかまわない場合は、Eclipse コレクションzipWithIndexあり、forEachWithIndexさまざまなタイプで使用できます。JDK タイプと Eclipse Collections タイプの両方を使用した、この課題に対する一連のソリューションを次に示します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 Collections のコミッターです。

于 2016-02-28T06:36:20.873 に答える
2

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

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

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);値が見つからない場合に値を返したい場合に追加します。

于 2018-04-04T19:59:39.577 に答える
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();
}
于 2016-02-14T02:14:03.490 に答える