5
\$\begingroup\$

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);
});
\$\endgroup\$
14
  • 1
    \$\begingroup\$ It can be done. But wouldn't it be better to have a Project class and a WorkType class with getters for the fields? Then you can have a List<Project> which, based on the above, would have two instances. \$\endgroup\$
    – WJS
    Jul 17, 2020 at 21:01
  • \$\begingroup\$ Thanks for the reply.. You are correct but the reason I did not map it to a POJO is because the method that does this should be sort of generic as in the group by keys (in this scenario its project and workType is parameterized) and I don't want to use reflection or anything like that yet.. However, if its simpler, it would be great if you could show me the way you would do it. Even if you use POJO's its fine. I'll do my best to translate it to my usecase \$\endgroup\$
    – user9492428
    Jul 17, 2020 at 21:06
  • \$\begingroup\$ I will also post an update to the question soon with the current progress. Thanks for your time \$\endgroup\$
    – user9492428
    Jul 17, 2020 at 21:08
  • \$\begingroup\$ You say The below gives me 75% of the expected result - What's in the missing 25%? And your question needs more focus; right now its goal is too broad/complex. Please edit your question to isolate the specific problem and the smallest amount of codein which the problem exists. The context (json etc) of your question seems irrelevant to your problem, which cold be described using POJO's, thus simlifying the problem statement and the code. See MCVE. \$\endgroup\$
    – Bohemian
    Jul 17, 2020 at 22:14
  • 1
    \$\begingroup\$ @Bohemian I found out streams was just to cumbersome (at least for me) But I did it in one pass using various compute constructs of map. I'll save my results and follow the question for changes in its status. \$\endgroup\$
    – WJS
    Jul 17, 2020 at 22:54

1 Answer 1

1
\$\begingroup\$

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
    }
]
\$\endgroup\$