我想根据Stream
的内容将单个Stream
拆分为Streams
Streams
。结果Stream
应该包含原始流的一部分'数据。
我真正的应用程序更复杂(它将日志行分组在时间间隔列表中),但我的问题是如何处理流,所以在这里我要问一个简化的例子。
我希望能够根据重复的相同数字将Stream<Integer>
拆分为Stream<Stream<Integer>>
,只留下奇数的流。
例如,以下流包含:
{1,1,1,2,2,2,3,6,7,7,1,1}
需要产生包含以下内容的流流:
{{1,1,1},{3},{7,7},{1,1}}
通过使用过滤器开始(或结束),我可以做出偶数:
Stream<Integer> input = ...;
Straem<Stream<Integer>> output = input.filter(this::isOdd).someOtherOperation();
这是不受欢迎的,因为它意味着每次评估每个输入值,这是可以接受的,但我宁愿避免这种情况。
我当前的解决方案会迭代流的内容并创建List<List<Integer>>
并将其转换为Stream<Stream<Integer>>
。但是这意味着完整的结果会保留在内存中(这对我的应用程序来说是不受欢迎的)。
我也认为我可以通过编写自己从流中读取的Iterator
来解决这个问题,但我不确定这是如何工作的。
如何根据原始Stream
的内容将Stream
转换为Streams
Stream
,而不将完整结果存储为{{1}首先是List
。
答案 0 :(得分:5)
您可能希望实施自己的aggregating spliterator来执行此操作。 proton-pack库中已有类似内容(第一个链接重定向到proton-pack中实现的链接)。
请注意,您获得了Stream<List<Integer>>
(您可能会尝试直接修改实现以获得Stream<Stream<Integer>>
,但您始终需要缓冲少量元素;具体取决于窗口的大小;要测试是否应该创建一个新窗口)。例如:
StreamUtils.aggregate(Stream.of(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1),
Objects::equals)
.forEach(System.out::println);
输出:
[1, 1, 1]
[2, 2, 2]
[3]
[6]
[7, 7]
[1, 1]
答案 1 :(得分:3)
您可以使用我的StreamEx
库。它有groupRuns
来完成工作:
List<Integer> input = Arrays.asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.groupRuns(Integer::equals)
.map(List::stream);
用法示例:
streams.map(s -> StreamEx.of(s).joining(",")).forEach(System.out::println);
输出:
1,1,1
3
7,7
1,1
与protonpack库类似,里面有自定义分裂器,但是使用StreamEx可以利用并行处理(质子包根本不会分裂)。
在顺序处理中,一次最多只有一个中间列表驻留在内存中(其他中间列表符合GC条件)。如果您仍然担心内存消耗(例如,您有很长的组),那么自StreamEx 0.3.3以来有另一种方法可以解决此任务:
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.runLengths()
.mapKeyValue(StreamEx::constant);
runLengths
方法返回条目流,其中key是元素,value是相邻重复元素的数量。之后使用StreamEx.constant
,这是Stream.generate(() -> value).limit(length)
的快捷方式。因此,即使对于很长的组,您也会有一个恒定的中间内存消耗。当然这个版本也是并行友好的。
更新: StreamEx 0.3.3已经发布,因此第二个解决方案现在也符合条件。
答案 2 :(得分:2)
我担心这是不可行的,至少不是很好的方式。即使您将元素映射到流中并减少它们,这些内部流也必须知道它们包含哪些元素,因此它们必须存储一些内容。
最简单的解决方案是使用groupingBy
,但它会将所有结果存储在地图中:
List<Integer> input = asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Map<Integer, List<Integer>> grouped = input.stream().collect(groupingBy(i -> i));
Stream<Stream<Integer>> streamOfStreams = grouped.values().stream().map(list -> list.stream());
您可以尝试使用reduce
操作,但它需要您实现自己的Stream of Streams,您必须在其中存储每个流包含的元素。更不用说实施它需要付出很多努力。
我能想到的最佳解决方案是迭代列表两次:
public static void main(String[] args) {
List<Integer> input = asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
input.stream().distinct().filter(i -> isOdd(i)).forEach(i -> {
List<Integer> subList = input.stream().filter(j -> Objects.equals(j, i)).collect(toList());
System.out.println(subList); // do something with the stream instead of collecting to list
});
}
private static boolean isOdd(Integer i) {
return (i & 1) == 1;
}
但请注意,它的时间复杂度为O(n^2)
。
修改强>
此解决方案仅包含本地元素组。它只存储当前的本地组。
public static void main(String[] args) {
Stream<Integer> input = Stream.of(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Iterator<Integer> iterator = input.iterator();
int first;
int second = iterator.next();
List<Integer> buffer = new ArrayList<>();
buffer.add(second);
do {
first = second;
second = iterator.next();
if (Objects.equals(first, second)) {
buffer.add(second);
} else {
doSomethingWithTheGroup(buffer);
buffer = new ArrayList<>(); // let GC remove the previous buffer
buffer.add(second);
}
} while (iterator.hasNext());
doSomethingWithTheGroup(buffer);
}
private static void doSomethingWithTheGroup(List<Integer> buffer) {
System.out.println(buffer);
}
private static boolean isOdd(Integer i) {
return (i & 1) == 1;
}
输出:
[1, 1, 1]
[2, 2, 2]
[3]
[6]
[7, 7]
[1, 1]
答案 3 :(得分:-1)
与@Jaroslaw一样,我也使用Map来保存不同的Streams。但是, 可以使地图保留从输入构建的Streams,而不是预先收集的。使用Stream.concat
和Stream.of
,您可以向流中添加一个元素:
Map<Integer, Stream<Integer>> streamMap = new HashMap<>();
int[] arr = {1,1,1,2,2,2,3,6,7,7,1,1};
Arrays.stream(arr)
.filter(this::isOdd)
.forEach(i -> {
Stream<Integer> st = streamMap.get(i);
if (st == null) st = Stream.of(i);
else st = Stream.concat(st, Stream.of(i));
streamMap.put(i, st);
});
streamMap.entrySet().stream().forEach(e -> {
System.out.print(e.getKey() + "={");
e.getValue().forEach(System.out::print);
System.out.println("}");
});
输出:
1={11111}
3={3}
7={77}