java 8 grouping by with distinct count

EdeGerSil :
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

Doubts how to make a map using java8 to store the following result. Map key must be field field1 and map value must be total_field2 field.

That is, I need to group my list using field field1 and count field field2

For the total field I have

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

My result is correct total_field1: {4=55, 6=31}

For field2, I needed something like this, but it's just giving me a record

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));
    }

Result total_Field2: {4=31}

should return me 2 example records total_Field2: {4=31, 6=31}

Example @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())));
Naman :

An alternate to Deadpool's answer is to count distinctByKey after groupingBy field1 while mapping to entries and then finally collecting to a Map as:

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));

If you were on Java-9 or above, you could have used Collectors.filtering as a downstream with the Predicate defined using the utility distinctByKey such as :

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


Note: The above two approaches are quite different though, the former groups all the list items by one field (field1) and then within each subgroup finds a distinct count by another specific field(field2).

On the other hand, the latter groups all the distinct items by the key(field2) and then groups these by another key(field1) with counting reduction.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=305202&siteId=1