Java 8中的分组和去重计数

7
SELECT Count(1) AS total,
          'hello' AS filter,
          field1 AS field1,
          Count(DISTINCT field2) AS total_field2
   FROM table
   WHERE field = true
     AND status = 'ok'
      GROUP  BY field1

有疑问如何使用Java 8制作地图来存储以下结果。地图键必须是字段field1,地图值必须是字段total_field2

也就是说,我需要使用字段field1对我的列表进行分组,并计算字段field2的数量。

对于总字段,我有以下内容:

myList.stream().collect(Collectors.groupingBy(MyObject::getField1, Collectors.counting())) 
// this is just counting the records grouped by field1

我的结果是正确的 total_field1: {4=55, 6=31}

对于field2,我需要类似这样的东西,但它只给我一条记录

myList.stream().filter(distinctByKey(MyObject::getField2))
.collect(Collectors.groupingBy(MyObject::getField1, Collectors.counting()));

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }

结果 total_Field2: {4=31}

应返回我2个示例记录 total_Field2: {4=31, 6=31}

示例 @Naman

public static <T, A, R> Collector<T, ?, R> filtering(
        Predicate<? super T> predicate, Collector<? super T, A, R> downstream) {

        BiConsumer<A, ? super T> accumulator = downstream.accumulator();
        return Collector.of(downstream.supplier(),
            (r, t) -> { if(predicate.test(t)) accumulator.accept(r, t); },
            downstream.combiner(), downstream.finisher(),
            downstream.characteristics().toArray(new Collector.Characteristics[0]));
    }

myList.stream().collect(Collectors.groupingBy(MyObject::getField1, filtering(distinctByKey(MyObject::getField2), Collectors.counting())));

3
你忘记了提问。 - Turing85
2
请记住 [询问] - JoSSte
如果我理解正确,您想按field1分组,并对field2进行不重复计数,是吗? - Ryuzaki L
@Deadpool,这就是我想要实现的,但我还没有成功。 - Ger
@JoSSte 抱歉,我会改进一个问题。 - Ger
3个回答

7

实际上,我使用了Set来消除重复项,并使用Collectors.collectingAndThen获取大小。

Map<String, Integer> res =  list.stream()
                                .collect(Collectors.groupingBy(MyObject::getField1, 
                                        Collectors.mapping(MyObject::getField2, 
                                            Collectors.collectingAndThen(Collectors.toSet(), set->set.size()))));

根据@Naman的建议,您还可以使用方法引用Set::size
Collectors.collectingAndThen(Collectors.toSet(), Set::size))));

2
进一步设置set->set.size()Set::size - Naman
2
这是个好答案 - jacky

2

除了Deadpool的答案,另一种方法是在按field1分组并映射到条目后计算distinctByKey,最后收集到一个Map中:

Map<String, Long> r = myList.stream()
        .collect(Collectors.groupingBy(MyObject::getField1))
        .entrySet().stream()
        .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(),
                e.getValue().stream().filter(distinctByKey(MyObject::getField2)).count()))
        .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));

如果您使用的是Java-9或更新版本,您可以使用Collectors.filtering作为下游操作,并使用实用程序distinctByKey定义的Predicate,例如:

Map<String, Long> result = myList.stream()
        .collect(Collectors.groupingBy(MyObject::getField1,
                Collectors.filtering(distinctByKey(MyObject::getField2),
                        Collectors.counting())));

注意:尽管以上两种方法非常不同,前者通过一个字段(field1)将所有列表项分组,然后在每个子组中通过另一个特定字段(field2)找到不同的计数。

另一方面,后者通过键(field2)将所有不同的项分组,然后通过另一个键(field1)进行计数缩减。


成功将两条记录合并,但其中一条被重置了 {4=31, 6=0},应该将6与31一起合并。 - Ger
1
@EdeGerSil 是的,我的错。Collectors.filtering 会从源中过滤对象作为实际集合,并且几乎与在此处使用 filter(distinctByKey(MyObject::getField2)) 相同。 - Naman
抱歉,@Naman,我不理解解决方案。我根据留下对Java8的参考制作了示例。如果可以帮助,我在我的问题中插入了我所做的编码。 - Ger

0
你可以尝试这个:
myList.stream().map(obj -> Pair.of(obj.getField1(), obj.getField2()))
      .distinct()
      .collect(Collectors.groupingBy(Pair::getLeft, counting()));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接