Home > OS >  How can I do a cumulative multiplication on an object data inside a map using its key?
How can I do a cumulative multiplication on an object data inside a map using its key?

Time:10-15

This is the Records class:

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

Map<String, List<Records>> if a key is having multiple values, I want to perform cumulative multiplication on those values and return a single entry for that key.

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

This is the code that I tried

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

CodePudding user response:

You basically have the right idea. You just seem to be making it more complicated than it needs to be.

The below code was developed using JDK 19.
(More notes after the code.)

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Records {
    private BigDecimal price;
    private BigDecimal $float;
    private BigDecimal share;

    public Records(BigDecimal thePrice, BigDecimal theFloat, BigDecimal theShare) {
        price = thePrice;
        $float = theFloat;
        share = theShare;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public BigDecimal getFloat() {
        return $float;
    }

    public BigDecimal getShare() {
        return share;
    }

    public String toString() {
        return String.format("price: %.2f , float: %.2f , share: %.2f", price, $float, share);
    }

    public static void main(String[] args) {
        List<Records> id1List = List.of(new Records(new BigDecimal(3),
                                                    new BigDecimal(4),
                                                    new BigDecimal(5)),
                                        new Records(new BigDecimal(2),
                                                    new BigDecimal(3),
                                                    new BigDecimal(6)));
        List<Records> id2List = List.of(new Records(new BigDecimal(1),
                                                    new BigDecimal(7),
                                                    new BigDecimal(4)));
        Map<String, List<Records>> map = new HashMap<>();
        map.put("Id1", id1List);
        map.put("Id2", id2List);
        for (Map.Entry<String, List<Records>> entry : map.entrySet()) {
            List<Records> list = entry.getValue();
            int size = list.size();
            if (size > 1) {
                Records record = list.get(0);
                BigDecimal thePrice = record.getPrice();
                BigDecimal theFloat = record.getFloat();
                BigDecimal theShare = record.getShare();
                for (int i = 1; i < size; i  ) {
                    record = list.get(i);
                    thePrice = thePrice.multiply(record.getPrice());
                    theFloat = theFloat.multiply(record.getFloat());
                    theShare = theShare.multiply(record.getShare());
                }
                map.put(entry.getKey(), List.of(new Records(thePrice, theFloat, theShare)));
            }
        }
        System.out.println(map);
    }
}
  • You can't use float as an identifier in Java since it is a primitive type. Therefore I changed it to $float.
  • You don't need to iterate the set of keys (from the Map), you can iterate the entries. Each entry contains both the key and the value.
  • I am not familiar with class DecimalUtil but class BigDecimal can do the multiplication.

Running the above code produces the following output:

{Id2=[price: 1.00 , float: 7.00 , share: 4.00], Id1=[price: 6.00 , float: 12.00 , share: 30.00]}

CodePudding user response:

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

  •  Tags:  
  • java
  • Related