[英]Count the same items in a row in Java 8 Stream API
我有一颗豆子和一条溪流
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 对象流的流应用一些中间操作。 在此示例中,必须有两个:("a", 3)、("b", 3) 和 ("a", 2)。
请认为这是一个非常简单的例子。 实际上,将有比仅仅连续计算相同值更复杂的逻辑。 实际上,我尝试设计一个简单的解析器,它接受一个标记流并返回一个对象流。
另请注意,它必须保持一个流(没有中间累积),并且在此示例中,它必须真正连续计算相同的值(它与分组不同)。
将感谢您对此任务解决方案的一般方法的建议。
Map<String, Long> result = src.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(result);
这将给出所需的输出
a=4, b=3
然后,您可以继续遍历 map 并创建TokenBag
的对象。
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());
首先将其分组到 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());
您需要将流转换为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
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);
如果您在TokenBag
中覆盖toString()
,则输出为:
TokenBag{token='a', count=3}
TokenBag{token='b', count=2}
TokenBag{token='a', count=2}
关于并行性的说明:我不知道如何并行化这个部分减少任务,我什至不知道它是否可能。 但如果是这样,我怀疑它会产生任何可衡量的改进。
创建地图,然后将地图收集到列表中:
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());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.