问题

我有一个由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));

我还可以看到如何减少此流以获取,例如,两个列表代表数据集的两个随机半部分,然后将它们转换回流。但是,有没有直接的方法从最初的流生成两个流?就像是

(heads, tails) = stream.[some kind of split based on filter]

感谢你的任何见解。


#1 热门回答(196 赞)

可以使用Acollector。

  • 对于两个类别,请使用Collectors.partitioningBy()工厂。

这将创建一个MapBooleanList,并根据aPredicate将项目放在一个或另一个列表中。

注意:由于流需要整体使用,因此无法在无限流上运行。因为无论如何都要使用流,所以此方法只是将它们放入列表中,而不是使用内存创建新的流。

此外,不需要迭代器,甚至不需要你提供的头部示例。

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,那么这个.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();

#2 热门回答(16 赞)

不幸的是,你所要求的是在JavaDoc of Stream中直接不赞成的:

只应对一个流进行操作(调用中间或终端流操作)一次。例如,这排除了"分叉"流,其中相同的源提供两个或更多个管道,或者同一个流的多个遍历。

如果你真的希望这种行为,你可以使用peek或其他方法解决这个问题。在这种情况下,你应该做的不是尝试使用分叉过滤器从同一原始Stream源备份两个流,而是复制流并适当地过滤每个重复项。

但是,你可能希望重新考虑aStream是否适合你的用例。


#3 热门回答(9 赞)

不完全是。你不可能得到两个Stream;这没有意义 - 如何在不需要同时生成另一个的情况下迭代一个?流只能运行一次。

但是,如果要将它们转储到列表或其他内容中,则可以执行此操作

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));

原文链接