我有一个bean和一个流
public class TokenBag {
private String token;
private int count;
// Standard constructor and getters here
}
Stream<String> src = Stream.of("a", "a", "a", "b", "b", "a", "a");
并希望将一些中间操作应用于返回TokenBag的另一个对象流的流。在这个例子中必须有两个:(&#34; a&#34;,3),(&#34; b&#34;,3)和(&#34; a&#34;,2)。
请认为这是一个非常简单的例子。实际上,逻辑将比连续计算相同的值更复杂。实际上,我尝试设计一个简单的解析器,它接受一个令牌流并返回一个对象流。
另请注意,它必须保留一个流(没有中间累积),并且在此示例中它必须真正计算连续的相同值(它与分组不同)。
非常感谢您对此任务解决方案的一般方法的建议。
答案 0 :(得分:4)
Map<String, Long> result = src.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(result);
这将提供所需的输出
a=4, b=3
然后,您可以继续迭代地图并创建TokenBag
的对象。
答案 1 :(得分:2)
Stream<String> src = Stream.of("a", "a", "a", "a", "b", "b", "b");
// collect to map
Map<String, Long> counted = src
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// collect to list
List<TokenBag> tokenBags = counted.entrySet().stream().map(m -> new TokenBag(m.getKey(), m.getValue().intValue()))
.collect(Collectors.toList());
答案 2 :(得分:2)
首先将其分组到Map,然后将条目映射到TokenBag:
Map<String, Long> values = src.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
List<TokenBag> tokenBags = values.entrySet().stream().map(entry -> {
TokenBag tb = new TokenBag();
tb.setToken(entry.getKey());
tb.setCount(entry.getValue().intValue());
return tb;
}).collect(Collectors.toList());
答案 3 :(得分:0)
创建地图,然后将地图收集到列表中:
Stream<String> src = Stream.of("a", "a", "a", "a", "b", "b", "b");
Map<String, Long> m = src.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
m.entrySet().stream().map(e -> new TokenBag(e.getKey(), e.getValue().intValue())).collect(Collectors.toList());
答案 4 :(得分:0)
您需要将您的流转换为Spliterator
,然后根据您的逻辑将此拆分器调整为部分减少某些元素的自定义符号(在您的示例中,它会需要计算相等的元素,直到出现不同的元素)。然后,您需要将分裂器转回新流。
请记住,这不是100%懒惰,因为您需要急切地使用支持流中的某些元素,以便为新流创建新的TokenBag
元素。
以下是自定义分裂器的代码:
public class CountingSpliterator
extends Spliterators.AbstractSpliterator<TokenBag>
implements Consumer<String> {
private final Spliterator<String> source;
private String currentToken;
private String previousToken;
private int tokenCount = 0;
private boolean tokenHasChanged;
public CountingSpliterator(Spliterator<String> source) {
super(source.estimateSize(), source.characteristics());
this.source = source;
}
@Override
public boolean tryAdvance(Consumer<? super TokenBag> action) {
while (source.tryAdvance(this)) {
if (tokenHasChanged) {
action.accept(new TokenBag(previousToken, tokenCount));
tokenCount = 1;
return true;
}
}
if (tokenCount > 0) {
action.accept(new TokenBag(currentToken, tokenCount));
tokenCount = 0;
return true;
}
return false;
}
@Override
public void accept(String newToken) {
if (currentToken != null) {
previousToken = currentToken;
}
currentToken = newToken;
if (previousToken != null && !previousToken.equals(currentToken)) {
tokenHasChanged = true;
} else {
tokenCount++;
tokenHasChanged = false;
}
}
}
因此,此分词器扩展Spliterators.AbstractSpliterator
并实现Consumer
。代码非常复杂,但其想法是它将来自源分裂器的一个或多个令牌调整为TokenBag
的实例。
对于来自源分裂器的每个接受的令牌,该令牌的计数递增,直到令牌更改为止。此时,使用令牌和计数创建TokenBag
实例,并立即将其推送到Consumer<? super TokenBag> action
参数。此外,计数器重置为1
。 accept
方法中的逻辑处理令牌更改,边界情况等。
以下是你应该如何应对这个分裂者:
Stream<String> src = Stream.of("a", "a", "a", "b", "b", "a", "a");
Stream<TokenBag> stream = StreamSupport.stream(
new CountingSpliterator(src.spliterator()),
false); // false means sequential, we don't want parallel!
stream.forEach(System.out::println);
如果您覆盖toString()
中的TokenBag
,则输出为:
TokenBag{token='a', count=3}
TokenBag{token='b', count=2}
TokenBag{token='a', count=2}
关于并行性的说明:我不知道如何并行化这个部分减少任务,我甚至不知道它是否完全可能。但如果是的话,我怀疑它会产生任何可衡量的改善。