Java 8ストリームで表されるデータセットがあります。
Stream<T> stream = ...;
私はそれをフィルタリングしてランダムなサブセットを取得する方法を見ることができます-例えば
Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));
また、このストリームを削減して、たとえば、データセットの2つのランダムな半分を表す2つのリストを取得し、それらをストリームに戻す方法も確認できます。しかし、最初のものから2つのストリームを生成する直接的な方法はありますか?何かのようなもの
(heads, tails) = stream.[some kind of split based on filter]
洞察力をありがとう。
ではない正確に。 1つから2つのStream
sを取得することはできません。これは意味がありません-同時に他を生成する必要なく、どのように一方を反復しますか?ストリームは一度だけ操作できます。
ただし、それらをリストなどにダンプする場合は、
stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));
これにはcollectorを使用できます。
Collectors.partitioningBy()
factoryを使用します。これにより、Map
からBoolean
までのList
が作成され、Predicate
に基づいていずれかのリストにアイテムが配置されます。
注:ストリーム全体を消費する必要があるため、無限ストリームでは機能しません。ストリームはとにかく消費されるため、このメソッドは、メモリ付きの新しいストリームを作成するのではなく、単にそれらをリストに入れます。
また、指定したヘッドのみの例でも、イテレーターは不要です。
Random r = new Random();
Map<Boolean, List<String>> groups = stream
.collect(Collectors.partitioningBy(x -> r.nextBoolean()));
System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
Collectors.groupingBy()
ファクトリを使用してください。Map<Object, List<String>> groups = stream
.collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());
ストリームがStream
ではなく、IntStream
のようなプリミティブストリームの1つである場合、この.collect(Collectors)
メソッドは使用できません。コレクターファクトリなしで手動で行う必要があります。実装は次のようになります。
IntStream intStream = IntStream.iterate(0, i -> i + 1).limit(1000000);
Predicate<Integer> p = x -> r.nextBoolean();
Map<Boolean, List<Integer>> groups = intStream.collect(() -> {
Map<Boolean, List<Integer>> map = new HashMap<>();
map.put(false, new ArrayList<>());
map.put(true, new ArrayList<>());
return map;
}, (map, x) -> {
boolean partition = p.test(x);
List<Integer> list = map.get(partition);
list.add(x);
}, (map1, map2) -> {
map1.get(false).addAll(map2.get(false));
map1.get(true).addAll(map2.get(true));
});
System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
編集
指摘したように、上記の「回避策」はスレッドセーフではありません。収集する前に通常のStream
に変換する方法があります。
Stream<Integer> stream = intStream.boxed();
残念ながら、あなたが求めるのは Stream of JavaDoc で直接眉をひそめられています:
ストリームの操作(中間または端末ストリーム操作の呼び出し)は1回のみです。これにより、たとえば、同じソースが2つ以上のパイプラインをフィードする「分岐」ストリーム、または同じストリームの複数のトラバーサルが除外されます。
peek
または他のメソッドを使用して、このタイプの動作を本当に望む場合は、これを回避できます。この場合、行う必要があるのは、分岐フィルターを使用して同じ元のStreamソースから2つのストリームをバックアップしようとする代わりに、ストリームを複製し、各複製を適切にフィルター処理することです。
ただし、Stream
がユースケースに適した構造である場合は、再検討することをお勧めします。
私は自分自身にこの質問に出くわし、分岐したストリームには有効であると証明できるいくつかのユースケースがあると感じています。消費者として以下のコードを書いたので、何もしませんが、関数や出会うかもしれない他のものに適用できます。
class PredicateSplitterConsumer<T> implements Consumer<T>
{
private Predicate<T> predicate;
private Consumer<T> positiveConsumer;
private Consumer<T> negativeConsumer;
public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
{
this.predicate = predicate;
this.positiveConsumer = positive;
this.negativeConsumer = negative;
}
@Override
public void accept(T t)
{
if (predicate.test(t))
{
positiveConsumer.accept(t);
}
else
{
negativeConsumer.accept(t);
}
}
}
これで、コードの実装は次のようになります。
personsArray.forEach(
new PredicateSplitterConsumer<>(
person -> person.getDateOfBirth().isPresent(),
person -> System.out.println(person.getName()),
person -> System.out.println(person.getName() + " does not have Date of birth")));
これは、Streamの一般的なメカニズムに反します。必要に応じて、ストリームS0をSaとSbに分割できるとします。 Saでcount()
などの端末操作を実行すると、必ずS0のすべての要素が「消費」されます。そのため、Sbはデータソースを失いました。
以前は、Streamにはtee()
メソッドがあり、ストリームを2つに複製していました。今は削除されました。
ストリームにはpeek()メソッドがありますが、それを使用して要件を達成できる場合があります。
正確ではありませんが、Collectors.groupingBy()
を呼び出すことで必要なことを達成できる場合があります。新しいコレクションを作成すると、その新しいコレクションでストリームをインスタンス化できます。
これは、私が思いつくことのできる最も悪い答えでした。
import org.Apache.commons.lang3.Tuple.ImmutablePair;
import org.Apache.commons.lang3.Tuple.Pair;
public class Test {
public static <T, L, R> Pair<L, R> splitStream(Stream<T> inputStream, Predicate<T> predicate,
Function<Stream<T>, L> trueStreamProcessor, Function<Stream<T>, R> falseStreamProcessor) {
Map<Boolean, List<T>> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));
L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());
R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());
return new ImmutablePair<L, R>(trueResult, falseResult);
}
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(10);
Pair<List<Integer>, String> results = splitStream(stream,
n -> n > 5,
s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),
s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));
System.out.println(results);
}
}
これは整数のストリームを取り、それらを5で分割します。5を超える場合、偶数のみをフィルタリングし、リストに入れます。残りは、|でそれらを結合します。
出力:
([6, 8],0|1|2|3|4|5)
ストリームを壊す(そして引数が多すぎる!)中間コレクションにすべてを収集するため、理想的ではありません。
ストリームから特定の要素をフィルタリングしてエラーとして記録する方法を探しているときに、私はこの質問に出くわしました。したがって、早すぎる終了アクションを控えめな構文で述部に添付するほど、ストリームを分割する必要はありませんでした。これは私が思いついたものです:
public class MyProcess {
/* Return a Predicate that performs a bail-out action on non-matching items. */
private static <T> Predicate<T> withAltAction(Predicate<T> pred, Consumer<T> altAction) {
return x -> {
if (pred.test(x)) {
return true;
}
altAction.accept(x);
return false;
};
/* Example usage in non-trivial pipeline */
public void processItems(Stream<Item> stream) {
stream.filter(Objects::nonNull)
.peek(this::logItem)
.map(Item::getSubItems)
.filter(withAltAction(SubItem::isValid,
i -> logError(i, "Invalid")))
.peek(this::logSubItem)
.filter(withAltAction(i -> i.size() > 10,
i -> logError(i, "Too large")))
.map(SubItem::toDisplayItem)
.forEach(this::display);
}
}