TransWikia.com

Java group list of maps by value and insert as grouped list

Code Review Asked by user9492428 on December 31, 2021

Not sure if I am overcomplicating the requirement but this is what I need (for simplicity I will show a the List<Map<String, Object>> as a JSON):

[
    {
        "project": "Project1",
        "workType": "Dev",
        "taskTitle": "Dev Title 1",
        "effort": 150
    },
    {
        "project": "Project1",
        "workType": "Dev",
        "taskTitle": "Dev Title 2",
        "effort": 200
    },
    {
        "project": "Project1",
        "workType": "QA",
        "taskTitle": "QA Title 1",
        "effort": 50
    },
    {
        "project": "Project1",
        "workType": "QA",
        "taskTitle": "QA Title 2",
        "effort": 25
    },
    {
        "project": "Project2",
        "workType": "Dev",
        "taskTitle": "Dev Title 3",
        "effort": 300
    },
    {
        "project": "Project2",
        "workType": "Dev",
        "taskTitle": "Dev Title 4",
        "effort": 300
    },
    {
        "project": "Project2",
        "workType": "QA",
        "taskTitle": "QA Title 3",
        "effort": 125
    },
    {
        "project": "Project2",
        "workType": "QA",
        "taskTitle": "QA Title 4",
        "effort": 125
    }
]

I want to process a HashMap which is equivalent to the above to produce a HashMap equivalent to below:

[
    {
        "project": "Project1",
        "effort": 425, // sum of all efforts
        "types": [
            {
                "workType": "Dev",
                "effort": 350 // sum of all effort where workType == Dev
            },
            {
                "workType": "QA",
                "effort": 75 // sum of all effort where workType == QA
            }
        ]
    },
    {
        "project": "Project2",
        "effort": 850, // sum of all efforts
        "types": [
            {
                "workType": "Dev",
                "effort": 600 // sum of all effort where workType == Dev
            },
            {
                "workType": "QA",
                "effort": 250 // sum of all effort where workType == QA
            }
        ]
    }
]

This is the starting point of my attempt:

transformedData.stream().collect(
    Collectors.groupingBy(
        m -> m.get("project"),
        Collectors.mapping( m2 -> m2, Collectors.toList() )
    )
);

Unfortunately I am a bit stuck on how to proceed..

UPDATE: The below gives me the expected result but it does not look performance friendly at all..

Map<Object, List<Map<String, Object>>> appGrouping = transformedData.stream().collect(
    Collectors.groupingBy(
        m -> m.get("project"),
        Collectors.mapping(m2 -> m2, Collectors.toList())
    )
);

List<Map<String, Object>> finalData = new ArrayList<>();

appGrouping.forEach((k,v) -> {
    Map<String, Object> data = new HashMap<>();
    data.put("project", k);
    data.put("types", new ArrayList<>());
    v.stream().collect(
        Collectors.groupingBy(
            m -> m.get("workType"),
            Collectors.summingDouble(m -> getDoubleVal(m.get("effort")))
        )
    ).forEach((k2,v2) -> {
        Map<String, Object> data2 = new HashMap<>();
        data2.put("workType", k2);
        data2.put("effort", v2);
        getFromMap(data, "types", List.class).add(data2); // this is a util method I wrote to get a value from a map
        Double totalEffort = Objects.requireNonNull(getFromMap(data, "effort", Double.class)) + v2;
        data.put("effort", totalEffort);
    });
    finalData.add(data);
});

One Answer

Here is one way to get it in a single pass.

Map<String, Map<String, Object>> tempMap = new HashMap<String, Map<String, Object>>();

transformedData.forEach(i -> {
    Double currentEffort = (Double) i.get("effort");
    
    Map<String, Object> subMap = 
            tempMap.computeIfAbsent((String)i.get("project"), 
                    (k) -> new HashMap<String, Object>(){{
                        put("project", (String)i.get("project"));
                        put("effort", 0.0D);
                        put("types", new HashMap<String, Double>());}});
    
    subMap.merge("effort", currentEffort, (o, n) -> (Double)o + (Double)n);
    
    ((Map<String, Double>)subMap.get("types"))
        .merge((String) i.get("workType"), currentEffort, (o, n) -> (Double)o + (Double)n);
});
List<Map<String, Object>> finalData = (List<Map<String, Object>>) tempMap.values();

The finalData object converted to JSON (using your data above as input) is the below. I've changed the types object from a list of maps to a map of workType to effort.

[
    {
        "types": {
            "QA": 250.0,
            "Dev": 600.0
        },
        "project": "Project2",
        "effort": 850.0
    },
    {
        "types": {
            "QA": 75.0,
            "Dev": 350.0
        },
        "project": "Project1",
        "effort": 425.0
    }
]

Answered by jnorman on December 31, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP