Java8计数收集器附加信息

时间:2017-05-30 19:03:57

标签: java java-8 collectors

我正在使用java 8的计数收集器来获取有关值计数的信息。

前者; 如果我有一堆像

这样的流
Stream<String> doc1 = Stream.of("a", "b", "c", "b", "c");
Stream<String> doc2 = Stream.of("b", "c", "d");
Stream<Stream<String>> docs = Stream.of(doc1, doc2);

我可以通过

计算文档中每个单词的出现次数
List<Map<String, Long>> collect = docs
    .map(doc -> doc.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())))
    .collect(Collectors.toList());

这导致结构为

[
{a=1, b=2, c=2}, 
{b=1, c=1, d=1}
]

但是,我希望将计数与源自它的docId相关联。例如,我希望有一个结构

[
{a=(randId1, 1), b=(randId1, 2), c=(randId1, 2)}, 
{b=(randId2, 1), c=(randId2, 1), d=(randId2, 1)}
]

其中randId1randId2可以在运行时生成(我只需要一种方法来追溯到一个独特的源),而()表示来自Apache的Pair

我尝试将文档包装在Pair的{​​{1}}中,但我仍然坚持修改(docId, doc)替换

Collectors.counting()

如何以所需格式获得输出?

3 个答案:

答案 0 :(得分:5)

这不是很易读......我已将Pair替换为AbstractMap.SimpleEntry,因为它做同样的事情,而且我已经在我的类路径上了。

 List<Map<String, AbstractMap.SimpleEntry<Long, UUID>>> result = docs.map(doc -> doc.collect(Collectors.collectingAndThen(
            Collectors.groupingBy(Function.identity(), Collectors.counting()),
            map -> {
                UUID rand = UUID.randomUUID();
                return map.entrySet().stream().collect(Collectors.toMap(
                        Entry::getKey,
                        e -> new AbstractMap.SimpleEntry<>(e.getValue(), rand)));
            })))
            .collect(Collectors.toList());

    System.out.println(result);

这个的输出:

[{a=1=890d7276-efb7-41cc-bda7-f2dd2859e740, 
  b=2=890d7276-efb7-41cc-bda7-f2dd2859e740, 
  c=2=890d7276-efb7-41cc-bda7-f2dd2859e740}, 

 {b=1=888d78a5-0dea-4cb2-8686-c06c784d4c66, 
  c=1=888d78a5-0dea-4cb2-8686-c06c784d4c66, 
  d=1=888d78a5-0dea-4cb2-8686-c06c784d4c66}]

答案 1 :(得分:3)

这个怎么样?

List<Map<String, Pair<UUID, Long>>> collect = docs.map(doc -> {
    UUID id = UUID.randomUUID();
    return doc.collect(groupingBy(
        identity(),
    //  v--- adapting Collector<?,?,Long> to Collector<?,?,Pair>    
        collectingAndThen(counting(), n -> Pair.of(id, n))
    ));
}).collect(Collectors.toList());

我只需复制您的代码段,然后通过Collectors#collectingAndThen将您的上一个通用参数Long调整为Pair

              //  v--- the code need to edit is here
List<Map<String, Long>> collect = docs
.map(doc -> doc.collect(Collectors.groupingBy(Function.identity()
 //                    the code need to edit is here ---v
                                             ,Collectors.counting())))
.collect(Collectors.toList());

答案 2 :(得分:2)

我认为你可以这样做:

List<Map<String, Pair<UUID, Long>>> result = docs
    .map(doc -> Pair.of(UUID.randomUUID(), doc))
    .map(p -> p.getRight() // right: doc stream
        .map(word -> Pair.of(word, p.getLeft()))) // left: uuid
    .map(stream -> stream.collect(Collectors.toMap(
        Pair::getLeft, // word
        p -> Pair.of(p.getRight(), 1L), // right: uuid
        (p1, p2) -> Pair.of(p1.getLeft(), p1.getRight() + p2.getRight())))) // merge
    .collect(Collectors.toList());

我多次使用Pair.of来传递单词和随机文档ID。最后,我使用Collectors.toMap函数在键上发生碰撞时合并值。结果完全符合您的要求,即:

[{a=(fa843dec-3e02-4811-b34f-79949340b4c5,1), 
  b=(fa843dec-3e02-4811-b34f-79949340b4c5,2), 
  c=(fa843dec-3e02-4811-b34f-79949340b4c5,2)}, 
 {b=(dc2ad8c7-298a-433e-8b27-88bd3c8eaebb,1), 
  c=(dc2ad8c7-298a-433e-8b27-88bd3c8eaebb,1), 
  d=(dc2ad8c7-298a-433e-8b27-88bd3c8eaebb,1)}]

也许可以通过将收集内部流的代码移动到辅助方法来改进:

private Map<String, Pair<UUID, Long>> collectInnerDoc(
        Stream<Pair<String, UUID>> stream) {
    return stream.collect(Collectors.toMap(
        Pair::getLeft, // word
        p -> Pair.of(p.getRight(), 1L), // random doc id
        (p1, p2) -> Pair.of(p1.getLeft(), p1.getRight() + p2.getRight()))); // merge
}

然后,您可以使用此方法收集外部流:

List<Map<String, Pair<UUID, Long>>> result = docs
    .map(doc -> Pair.of(UUID.randomUUID(), doc))
    .map(p -> p.getRight() // right: doc stream
        .map(word -> Pair.of(word, p.getLeft()))) // left: uuid
    .map(this::collectInnerDoc) // map inner stream to map
    .collect(Collectors.toList());

这假设私有方法在您收集外部流的同一个类中声明。如果不是这种情况,请相应地更改this::collectInnerDocs方法参考。