Search code examples
javalistcollectionshashmap

How to acumulate properties of Objects assosiated with the same Key in a Map<K, List<V>> and transform it into Map<K, V>


I have a class Records as follows:

public class Records { 
    private BigDecimal price;
    private BigDecimal tos;
    private BigDecimal share;
}

And have a Map of type Map<String, List<Records>> where each Key can be associated with a group of Records.

I want to perform cumulative multiplication on those Records instances associated with the same Key and produce the resulting Map where every Key is mapped to a single Records instance.

For example, in my map the data, if I store data, is like this:

Map<String, List<Records>> has data

{
"Id1": [ { "price": 3, "share": 4, "tos": 5}, { "price": 2, "share": 3, "tos": 6} ],
"Id2": [ { "price": 1, "share": 7, "tos": 4} ]
}

I want to perform cumulative multiplication on data of Id1 and achieve output like:

Map<String, Records>

{
"Id1": { "price": 6, "share": 12, "tos": 30},
"Id2": { "price": 1, "share": 7, "tos": 4}
}

My attempt:

private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {

    Map<String, Records> recordData = null;
    Records output = null;

    for (Map.Entry<String, List<Records>> data : recordsMap.entrySet()) {

        List<Records> dbData = data.getValue();

        for (String pid : recordsMap.keySet()) {

            output = new Records();

            if(recordsMap.get(pid).size() > 1) {

                BigDecimal price = BigDecimal.ONE, share = BigDecimal.ONE, tos= BigDecimal.ONE;

                for (Records abc : dbData) {
                    price = DecimalUtil.resetValueWithDefaultScale(price .multiply(abc.getPrice()));
                    share = DecimalUtil.resetValueWithDefaultScale(share .multiply(abc.getShare()));
                    tos= DecimalUtil.resetValueWithDefaultScale(tos.multiply(abc.getTos()));
                }
                output.setPrice(price);
                output.setShare(share);
                output.setTos(tos);
            }
            recordData.put(pid, output);
        }
    }
    return recordData;
}

What I'm stuck on:

I'm not fully understand how I to store the multiplied data and along with that store the rest of the data.


Solution

  • It would be handy to define a method containing accumulation logic inside the Records class instead of manually manipulating the data outside the Records instance and then reassigning it via setter, which is less maintainable and violates the Information expert principle.

    public static class Records {
        private BigDecimal price = BigDecimal.ONE;
        private BigDecimal tos = BigDecimal.ONE;
        private BigDecimal share = BigDecimal.ONE;
        
        public void merge(Records other) {
            price = DecimalUtil.resetValueWithDefaultScale(price.multiply(other.getPrice()));
            share = DecimalUtil.resetValueWithDefaultScale(share.multiply(other.getShare()));
            tos = DecimalUtil.resetValueWithDefaultScale(fl.multiply(other.getTos()));
        }
        
        // getters, constructors, etc.
    }
    

    Now the code in the method that operates with a map of records can be made leaner.

    Note

    • That in order to perform the transformation, you've illustrated with the sample data you don't need a nested for-loop (the inner loop iterating over the keys in your code is redundant).
    • Since your objects are mutable, it might not be a good idea to share object between different collections (for that reason in the implementations provided below all Records instances are not reused, even if there's only one record mapped to a particular key).
    • Plural nouns like Records are rarely used as names-class. And in such cases it's either a utility class (like Collections) which is meant to facilitate various actions which can be performed with instances of a particular type or class which is meant to store multiple instances of particular type. But Records is neither of these, therefore I would rather call this class Record.

    That's your code can be reimplemented using classic Java features:

    private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {
    
        Map<String, Records> recordData = new HashMap<>();
        
        for (Map.Entry<String, List<Records>> entry : recordsMap.entrySet()) {
            Records value = new Records();
            recordData.put(entry.getKey(), value);
            for (Records next : entry.getValue()) {
                value.merge(next);
            }
        }
        return recordData;
    }
    

    That's how it can be implemented with Stream IPA in a more concise way using collector toMap() and three-args collect() to perform mutable reduction:

    private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {
        
        return recordsMap.entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().stream()
                    .collect( Records::new, Records::merge, Records::merge )
            ));
    }
    

    A link to Online Demo