deHaar
deHaar

Reputation: 18568

Convert Map<Integer, List<Object>> to Map<Integer, Map<String, Map<LocalDate, Integer>>> using Java stream API

I receive some result sets from a database and store each row as an object of type DbRow, this is the class:

public class DbRow {

    private int clientNumber;
    private String recordNumber;
    private String takeoverMonth;
    private int takeovers;
    private int mainClaims;
    private int distinctContractDates;
    private LocalDate contractDate;

    public DbRow(int clientNumber, String recordNumber, String takeoverMonth, int takeovers,
                int mainClaims, int distinctContractDates, LocalDate contractDate) {
        super();
        this.clientNumber = clientNumber;
        this.recordNumber = recordNumber;
        this.takeoverMonth = takeoverMonth;
        this.takeovers = takeovers;
        this.mainClaims = mainClaims;
        this.distinctContractDates = distinctContractDates;
        this.contractDate = contractDate;
    }

    // getters & setters

    // hashCode, equals & toString
}

I currently store the received objects in a Map<Integer, List<DbRow>> in order to have them separated by a certain property (in this case clientNumber, all the DbRows that have a certain client number are stored in a List<DbRow>> while their key is this certain client number).

I need to create a Map<Integer, Map<String, Map<LocalDate, Integer>>> out of it, which I currently do using a lot of lines of code.

The desired result can be described as
the count of distinct contract dates per takeover month per client number
(like Map<client number, Map<takeover month, Map<contract date, count>>>)
where the takeover month is a String of the format "yyyy/MM".

My current solution is ugly, but working. It contains the creation and filling of the submaps as well as checking for existing keys which I want to get rid of. Please have a look at it:

Map<Integer, List<DbRow>> records = new TreeMap<>();

// fill records with database results ...

Map<Integer, Map<String, Map<LocalDate, Integer>>> results = new TreeMap<>();

records.forEach((clientNumber, dbRows) -> {
    Map<String, List<DbRow>> rowsPerMonth = dbRows.stream()
                    .collect(Collectors.groupingBy(DbRow::getTakeoverMonth));
    Map<String, Map<LocalDate, Integer>> monthResults = new TreeMap<>();

    rowsPerMonth.forEach((takeoverMonth, rows) -> {
        if (monthResults.containsKey(takeoverMonth)) {
            Map<LocalDate, Integer> contractDateCount = monthResults.get(takeoverMonth);
            rows.forEach(dbRow -> {
                LocalDate contractDate = dbRow.getContractDate();
                if (contractDateCount.containsKey(contractDate)) {
                    int count = contractDateCount.get(contractDate);
                    count++;
                    contractDateCount.put(contractDate, count);
                } else {
                    contractDateCount.put(contractDate, 1);
                }
            });
            monthResults.put(takeoverMonth, contractDateCount);
        } else {
            Map<LocalDate, Integer> contractDateCount = new TreeMap<>();
            rows.forEach(dbRow -> {
                LocalDate contractDate = dbRow.getContractDate();
                if (contractDateCount.containsKey(contractDate)) {
                    int count = contractDateCount.get(contractDate);
                    count++;
                    contractDateCount.put(contractDate, count);
                } else {
                    contractDateCount.put(contractDate, 1);
                }
            });
            monthResults.put(takeoverMonth, contractDateCount);
        }
    });

    results.put(clientNumber, monthResults);
});

Does anyone know (if there is) a way to do it in a shorter way using the stream API? I really want to have a pretty solution for it.

Upvotes: 1

Views: 187

Answers (1)

Naman
Naman

Reputation: 31868

You're possible looking for something like :

Map<Integer, Map<String, Map<LocalDate, Long>>>  countDistinctDatesPerMonthPerClient(List<DbRow> input) {
    return input.stream()
            .collect(Collectors.groupingBy(DbRow::getClientNumber,
                    Collectors.groupingBy(DbRow::getTakeoverMonth,
                            Collectors.groupingBy(DbRow::getContractDate, Collectors.counting()))));
}

Upvotes: 2

Related Questions