让我们说我有一个Java 8流数组:Stream<T>[] streams
,我想制作一个Stream,其中新流的每个元素都是一个数组,通过从中选取一个元素每个初始基本流(假设它们都是顺序的)。
例如,如果我有:
streams [ 0 ] returning: ( "A", "B", "C" ),
streams [ 1 ] returning ( "X", "Y", "Z" )
and streams [ 2 ] as ( "0", "1", "2" )
我喜欢返回
的信息流 ( { "A", "X", "0" }, { "B", "Y", "1" }, { "C", "Z", "2" } )
是否有一些代码已经实现了这个?我知道如何做到这一点,这将是pair case的概括,但我想知道是否有可重复使用的东西。
编辑:对不起,我意识到我需要澄清一下:
我不想创建整个矩阵,我想要一个动态一次返回一行的流(第一个A / X / 0,然后是B / Y / 1等),必须事先占用所有行的记忆。我对基本流的大小有合理的假设(例如,采取最小值,一旦有没有更多元素返回的流就停止)。
我知道这可以通过首先将基本流转换为迭代器,然后创建一个新的迭代器来实现,其中next()从每个下划线迭代器中选择一个元素并返回一个新行。这就是我上面链接的那对示例,我可以用自己的方式实现它,在这里我试图理解它是否已经在某个库中完成(我知道JDK没有这样的功能) )。
答案 0 :(得分:3)
首先,保留一组流是一个非常糟糕的主意,因为它们无法重复使用,并且使已经很复杂的可能解决方案变得复杂。
不,这在普通的JDK中是不可能的。没有zip
功能,我们都没有Tuples
,所以我担心这是你能提出的最好的事情:
Stream[] streams = Stream.of(
Stream.of("A", "B", "C"),
Stream.of("X", "Y", "Z"),
Stream.of("0", "1", "2"))
.toArray(Stream[]::new);
String[][] arrays = Arrays.stream(streams)
.map(s -> s.toArray(String[]::new))
.toArray(String[][]::new);
int minSize = Arrays.stream(arrays)
.mapToInt(s -> s.length)
.min().orElse(0);
String[][] zipped = IntStream.range(0, minSize)
.mapToObj(i -> Arrays.stream(arrays)
.map(s -> s[i])
.toArray(String[]::new))
.toArray(String[][]::new);
首先,我们需要将一个数组流转换为数组或其他任何我们可以遍历多次的数组。
其次,如果数组中的流具有不同的长度,则没有指定要执行的操作,我假设标准zip
行为,只要我们可以从每个集合中提取元素,就会连接元素。
第三,我在这里创建了一个所有可能的索引流(IntStream.range(0, minSize)
)并从每个嵌套数组中手动提取元素。
在Optional上使用.get()是很好的,因为计算minSize可以保证那里会有东西。
假设我们正在处理列表列表,这是一种更合理的方法:
List<List<String>> lists = Arrays.asList(
Arrays.asList("A", "B", "C"),
Arrays.asList("X", "Y", "Z"),
Arrays.asList("0", "1", "2"));
final int minSize = lists.stream()
.mapToInt(List::size)
.min().orElse(0);
List<List<String>> result = IntStream.range(0, minSize)
.mapToObj(i -> lists.stream()
.map(s -> s.get(i))
.collect(Collectors.toList()))
.collect(Collectors.toList());
Java 9的Stream API添加可能允许我们放弃minSize
的计算。
如果您希望生成序列lazy
,则无法收集结果:
IntStream.range(0, minSize)
.mapToObj(i -> lists.stream()
.map(s -> s.get(i))
.collect(Collectors.toList()));
答案 1 :(得分:1)
如果您的意思是任意数量的Stream
作为输入 - 我可以想到的不是TupleX
,但如果您真的知道传入的流都是相同的大小(没有无限的流),那么这可能符合您的需求:
@SafeVarargs
static <T> Stream<Stream<T>> streamOfStreams(Stream<T>... streams) {
@SuppressWarnings("unchecked")
Iterator<T>[] iterators = new Iterator[streams.length];
for (int i = 0; i < streams.length; ++i) {
iterators[i] = streams[i].iterator();
}
Iterator<T> first = iterators[0];
Builder<Stream<T>> outer = Stream.builder();
Builder<T> inner = Stream.builder();
while (first.hasNext()) {
for (int i = 0; i < streams.length; ++i) {
inner.add(iterators[i].next());
}
outer.add(inner.build());
inner = Stream.builder();
}
return outer.build();
}
答案 2 :(得分:1)
自Guava版本21以来,您可以使用Streams.zip
实用程序方法,它可以执行您想要的操作,但它只适用于两个流。
现在,如果您将流数组转换为流数据流,则可以使用此Streams.zip
方法执行缩减:
Stream<List<String>> zipped = Arrays.stream(streams)
.map(s -> s.map(e -> {
List<String> l = new ArrayList<>();
l.add(e);
return l;
}))
.reduce((s1, s2) -> Streams.zip(s1, s2, (l1, l2) -> {
l1.addAll(l2);
return l1;
}))
.orElse(Stream.empty());
List<List<String>> tuples = zipped.collect(Collectors.toList());
System.out.println(tuples); // [[A, X, 0], [B, Y, 1], [C, Z, 2]]
请注意,在缩小之前,您需要将每个Stream<T>
映射到Stream<List<T>>
,以便您可以使用List.addAll
压缩流。
编辑:上面的代码有效,但我对其性能和内存占用情况表示严重担忧,主要是因为创建了一个单个元素的多个列表。
也许使用接受身份的Stream.reduce
版本,累加器和合并器可以更好地工作:
Stream<List<String>> zipped = Arrays.stream(streams)
.reduce(
IntStream.range(0, streams.length).mapToObj(n -> new ArrayList<>()),
(z, s) -> Streams.zip(z, s, (l, e) -> {
l.add(e);
return l;
}),
(s1, s2) -> Streams.zip(s1, s2, (l1, l2) -> {
l1.addAll(l2);
return l1;
}));
List<List<String>> tuples = zipped.collect(Collectors.toList());
System.out.println(tuples); // [[A, X, 0], [B, Y, 1], [C, Z, 2]]
标识必须是n
个空列表的流,n
是streams
数组的长度,而累加器使用Streams.zip
压缩流列表与元素流。组合器与以前保持一致:它使用Streams.zip
压缩两个列表流。
答案 3 :(得分:1)
单元测试中提供的用法示例(here和here),这些类是此utility package的一部分。
编辑:在Federico的评论之后,我添加了Spliterator实现,注意到基于Iterator
的版本不能并行。