ストリーム内のインデックスにアクセスしながらストリームを反復処理する簡潔な方法はありますか?
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は移動したか削除されたようです...
最もわかりやすい方法は、一連のインデックスから始めることです。
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());
並列ストリームで後者の方法を使用すると、項目が必ずしも「順番どおりに」処理されないため、破損する可能性があります 。
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からのコメントに従って、例を更新しました。
グアバ21以来、あなたが使用することができます
Streams.mapWithIndex()
例( 公式ドキュメント から):
Streams.mapWithIndex(
Stream.of("a", "b", "c"),
(str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
私は自分のプロジェクトで次の解決策を使用しました。可変オブジェクトや整数範囲を使用するよりも優れていると思います。
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));
}
}
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ストリームを構築します。
単純反応の等価物も同様になります。
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
単純反応バージョンは、非同期/並行処理に合わせて調整されています。
完全を期すために、ここに私の StreamEx libraryを使った解決策を示します。
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
ここではEntryStream<Integer, String>
を拡張し、 filterKeyValue
または values
のような特定の操作を追加する Stream<Entry<Integer, String>>
を作成します。 toList()
shortcutも使用されています。
リストを使えば試すことができます
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
ここでは、Streamがリストまたは配列で作成されたときに解決策を見つけました(そしてサイズがわかります)。しかし、Streamのサイズが不明の場合はどうなりますか?この場合、この変種を試してください。
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());
});
}
https://github.com/poetix/protonpack uでZipを実行できます。
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);
サードパーティーのライブラリーを使用しても構わない場合は、 Eclipse Collections に zipWithIndex
および forEachWithIndex
があります。これは、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コレクションのコミッターです
Stream
は他のStream
とは異なり、インデックスにアクセスしながらCollection
を反復処理する方法はありません。 ドキュメンテーション に記載されているように、Stream
は単にある場所から別の場所へデータを運ぶためのパイプラインです。
ストレージがありません。ストリームは要素を格納するデータ構造ではありません。代わりに、それらは計算操作のパイプラインを通してソース(データ構造、ジェネレータ、IOチャネルなど)からの値を伝えます。
もちろん、あなたがあなたの質問で暗示しているように見えるので、あなたはいつでもあなたのインデックスへのアクセスを持つことになるでしょうStream<V>
のようなCollection<V>
にあなたのList<V>
を変換することができます。
偶然Vavr(以前はJavaslangとして知られていました)を使用する場合は、専用の方法を利用することができます。
Stream.of("A", "B", "C")
.zipWithIndex();
コンテンツを印刷すると、おもしろいことがわかります。
Stream((A, 0), ?)
これはStreams
が遅延しており、ストリーム内の次の項目についての手がかりがないためです。
これは AbacusUtil によるコードです。
Stream.of(names).indexed()
.filter(e -> e.value().length() <= e.index())
.map(Indexed::value).toList();
開示:私はAbacusUtilの開発者です。
述語に基づいてインデックスを取得しようとしている場合は、これを試してください。
最初のインデックスだけに関心がある場合
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);
を追加してください。
この質問( 最初の要素と一致するインデックスを取得するための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;
}
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
インデックスを取得するには 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());
以下の例で必要に応じて、インデクサーをカプセル化するための静的内部クラスを作成できます。
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();
}