Java 8 collects, calculates with the sum

I have data in the following format:

ProductName | Date
------------|------
ABC         | 1-May
ABC         | 1-May
XYZ         | 1-May
ABC         | 2-May

It is in the form of a List, where Product consists of ProductName and Date. Now I wanted to group this data and get an invoice with the amount as follows:

1-May
  -> ABC : 2
  -> XYZ : 1
  -> Total : 3
2-May
  -> ABC: 1
  -> Total : 1

So far, I have achieved grouping with a count, but not a total cost.

myProductList.stream()
  .collect(Collectors.groupingBy(Product::getDate, 
            Collectors.groupingBy(Product::getProductName, Collectors.counting())));

I do not know how to get the general value.

+4
source share
4 answers

You can use Collectors.collectingAndThento add a shared record to each internal map:

Map<LocalDate, Map<String, Long>> result = myProductList.stream()
    .collect(Collectors.groupingBy(
        Product::getDate,
        TreeMap::new, // orders entries by key, i.e. by date
        Collectors.collectingAndThen(
            Collectors.groupingBy(
                Product::getProductName,
                LinkedHashMap::new,     // LinkedHashMap is mutable and 
                Collectors.counting()), // preserves insertion order, i.e.
            map -> {                    // we can insert the total later
                map.put("Total", map.values().stream().mapToLong(c -> c).sum());
                return map;
            })));

The map resultcontains:

{2017-05-01={ABC=2, XYZ=1, Total=3}, 2017-05-02={ABC=1, Total=1}}

, . - TreeMap, ( ). LinkedHashMap, , .. , .


. , , , , , . ( , map.values().stream().mapToLong(c -> c).sum()). , , 1 , . , :

public static <T, K> Collector<T, ?, Map<K, Long>> groupsWithTotal(
    Function<? super T, ? extends K> classifier,
    K totalKeyName) {

    class Acc {
        Map<K, Long> map = new LinkedHashMap<>();
        long total = 0L;

        void accumulate(T elem) {
            this.map.merge(classifier.apply(elem), 1L, Long::sum);
            this.total++;
        }

        Acc combine(Acc another) {
            another.map.forEach((k, v) -> {
                this.map.merge(k, v, Long::sum);
                this.total += v;
            });
            return this;
        }

        Map<K, Long> finish() {
            this.map.put(totalKeyName, total);
            return this.map;
        }
    }

    return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::finish);
}

(, Collectors.groupingBy(Product::getProductName, Collectors.counting())), . .

, groupsWithTotal:

Map<LocalDate, Map<String, Long>> result = myProductList.stream()
    .collect(Collectors.groupingBy(
        Product::getDate,
        TreeMap::new,
        groupsWithTotal(Product::getProductName, "Total")));

:

{2017-05-01={ABC=2, XYZ=1, Total=3}, 2017-05-02={ABC=1, Total=1}}

, LinkedHashMap null , null, , a Product null productName, null , NullPointerException.

+3

, , . , . Total , . , , .

public static <T, K, M extends Map<K, Long>> Collector<T, ?, M> countingGroups(
        Function<? super T, ? extends K> classifier, Supplier<? extends M> mapFactory, K totalKey) {
    return Collectors.collectingAndThen(
            Collectors.groupingBy(classifier, mapFactory, Collectors.counting()),
            m -> {
                long totalValue = m.values().stream().mapToLong(Long::longValue).sum();
                if (m.put(totalKey, totalValue) != null) {
                    throw new IllegalStateException("duplicate mapping found for total key");
                }
                return m;
            });
}

, :

Map<Date, Map<String, Long>> counts = myProductList.stream()
        .collect(Collectors.groupingBy(Product::getDate,
                countingGroups(Product::getProductName, HashMap::new, "Total")));
+1

, - :

Map<String, Map<String, Long>> products

() . :

Map<String, Long> totals = products.entrySet().stream()
            .collect(toMap(Entry::getKey,
                    e -> e.getValue().values().stream().mapToLong(i -> i).sum()));
0

, , . , Collectors.collectingAndThen.

CollectingAndThen is a special collector that allows you to perform another action on the result immediately after collecting the ends.

I used lombokto avoid wasting time while writing constructors and getter/setters.

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Getter @Setter
@AllArgsConstructor
class Product {
    private String name;
    private Long date;
}

public class Practice1 {
    public static void main(String... args) {
        final List<Product> list = new ArrayList<>();
        list.add(new Product("ABC", 1L));
        list.add(new Product("ABC", 1L));
        list.add(new Product("XYZ", 1L));
        list.add(new Product("ABC", 2L));

        Map<Long, Map<String, Long>> finalMap = list.stream()
                .collect(
                        Collectors.groupingBy(
                                Product::getDate,
                                Collectors.collectingAndThen(
                                        Collectors.groupingBy(Product::getName, Collectors.counting()),
                                        map -> {
                                            long sum = map.values().stream().reduce(0L, Long::sum);
                                            map.put("total", sum);
                                            return map;
                                        }
                                )

                        )
                );

        System.out.println(finalMap);
    }
}
0
source

Source: https://habr.com/ru/post/1677017/


All Articles