Search code examples
javajava-stream

How do I convert weekly data to a monthly data from a nested map structure


I have a some data in Java which is in the format Map<String, Map<String, Object>>

Let's say we have 2 countries and also assume that we have weekly data for each country, which means the last 60 days will have a data of something like this:

   {
    "US”= {
        "2023-01-02”={
            "customerViews"= 2500,
            "productSales"= 1200,
            "noOfUnitsSold"= 600,
            "conversion"= 0.24
        },
        "2023-01-09”={
            "customerViews"= 2900,
            "productSales"= 1400,
            "noOfUnitsSold"= 700,
            "conversion"= 0.24
        },
        "2023-01-16”= {
            "customerViews"= 2000,
            "productSales"= 1000,
            "noOfUnitsSold"= 500,
            "conversion"= 0.25
        },
        "2023-02-23”= {
            "customerViews"= 2700,
            "productSales"= 1300,
            "noOfUnitsSold"= 650,
            "conversion"= 0.22
        },
        "2023-01-30”={
            "customerViews"= 1800,
            "productSales"= 900,
            "noOfUnitsSold"= 450,
            "conversion"= 0.21
        },
        "2023-02-06”= {
            "customerViews"= 2300,
            "productSales"= 1100,
            "noOfUnitsSold"= 550,
            "conversion"= 0.23
        },
        "2023-02-13”= {
            "customerViews"= 2000,
            "productSales"= 1000,
            "noOfUnitsSold"= 500,
            "conversion"= 0.25
        },
        "2023-02-20”= {
            "customerViews"= 2500,
            "productSales"= 1200,
            "noOfUnitsSold"= 600,
            "conversion"= 0.24
        }
    },
    "CA”= {
        "2023-01-02”= {
            "customerViews"= 2000,
            "productSales"= 1000,
            "noOfUnitsSold"= 500,
            "conversion"= 0.24
        },
        "2023-01-23”= {
            "customerViews"= 2200,
            "productSales"= 1100,
            "noOfUnitsSold"= 550,
            "conversion"= 0.22
        },
        "2023-01-30”={
            "customerViews"= 1800,
            "productSales"= 900,
            "noOfUnitsSold"= 450,
            "conversion"= 0.22
        },
        "2023-02-06”= {
            "customerViews"= 1700,
            "productSales"= 850,
            "noOfUnitsSold"= 425,
            "conversion"= 0.21
        },
        "2023-02-13”= {
            "customerViews"= 2000,
            "productSales"= 1000,
            "noOfUnitsSold"= 500,
            "conversion"= 0.24
        }
      }
    }

The reason I didn't include few weeks of data for 2nd country is because, there might be a case where there are no data for that week. So in that case there will be no entries for that week.

Now, I want to take this data as a input and convert to a monthly data which in turn the response looks like this:

    {
    "US"= {
        "2023-01"= {
            "customerViews"= 12400.0,
            "productSales"= 6000.0,
            "noOfUnitsSold"= 3000.0,
            "conversion"= 1.18
        },
        "2023-02"= {
            "customerViews"= 6300.0,
            "productSales"= 3100.0,
            "noOfUnitsSold"= 1550.0,
            "conversion"= 0.7
        }
    },
    "CA"= {
        "2023-01"= {
            "customerViews"= 4150.0,
            "productSales"= 2050.0,
            "noOfUnitsSold"= 1025.0,
            "conversion"= 0.45
        },
        "2023-02"= {
            "customerViews"= 3500.0,
            "productSales"= 1750.0,
            "noOfUnitsSold"= 875.0,
            "conversion"= 0.43
        }
      }
    }

This is what I tried:

   import java.util.*;

     public class WeeklyToMonthlyConverter {
       public static Map<String, Map<String, Object>> convertToMonthlyData(Map<String,   Map<String, Object>> weeklyData) {
        Map<String, Map<String, Object>> monthlyData = new HashMap<>();

        for (Map.Entry<String, Map<String, Object>> countryEntry : weeklyData.entrySet()) {
            String countryId = countryEntry.getKey();
            Map<String, Object> weeklyMap = countryEntry.getValue();

            Map<String, Object> monthlyMap = new HashMap<>();
            for (Map.Entry<String, Object> weekEntry : weeklyMap.entrySet()) {
                String weekDate = weekEntry.getKey();
                Object weekData = weekEntry.getValue();

                String month = weekDate.substring(0, 7); // Extract the year and month

                if (monthlyMap.containsKey(month)) {
                    // If the month entry exists, sum the values
                    Map<String, Object> existingMonthData = (Map<String, Object>)    monthlyMap.get(month);
                    sumData(existingMonthData, (Map<String, Object>) weekData);
                } else {
                    // If the month entry does not exist, initialize it with the weekly data
                    monthlyMap.put(month, weekData);
                }
            }

            monthlyData.put(countryId, monthlyMap);
        }

        return monthlyData;
    }

    private static void sumData(Map<String, Object> targetMap, Map<String, Object> sourceMap) {
        for (Map.Entry<String, Object> entry : sourceMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof Number) {
                // If the value is numeric, sum it with the existing value
                if (targetMap.containsKey(key)) {
                    Number existingValue = (Number) targetMap.get(key);
                    double sum = existingValue.doubleValue() + ((Number) value).doubleValue();
                    targetMap.put(key, sum);
                } else {
                    targetMap.put(key, value);
                }
            }
        }
    }

    public static void main(String[] args) {
       
    Map<String, Map<String, Object>> weeklyData = new HashMap<>();

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

    usWeeklyData.put("2023-01-02", createWeeklyObject(1000, 500, 2000, 0.25));
    usWeeklyData.put("2023-01-09", createWeeklyObject(1200, 600, 2500, 0.24));
    usWeeklyData.put("2023-01-16", createWeeklyObject(1100, 550, 2300, 0.23));
    usWeeklyData.put("2023-01-23", createWeeklyObject(1300, 650, 2700, 0.22));
    usWeeklyData.put("2023-01-30", createWeeklyObject(1400, 700, 2900, 0.24));
    usWeeklyData.put("2023-02-06", createWeeklyObject(900, 450, 1800, 0.21));
    usWeeklyData.put("2023-02-13", createWeeklyObject(1000, 500, 2000, 0.25));
    usWeeklyData.put("2023-02-20", createWeeklyObject(1200, 600, 2500, 0.24));

    weeklyData.put("US", usWeeklyData);

    Map<String, Object> caWeeklyData = new HashMap<>();
    caWeeklyData.put("2023-01-02", createWeeklyObject(800, 400, 1800, 0.22));
    caWeeklyData.put("2023-01-23", createWeeklyObject(950, 475, 1950, 0.23));
    caWeeklyData.put("2023-01-30", createWeeklyObject(1100, 550, 2200, 0.22));
    caWeeklyData.put("2023-02-06", createWeeklyObject(850, 425, 1700, 0.21));
    caWeeklyData.put("2023-02-13", createWeeklyObject(900, 450, 1800, 0.22));

    weeklyData.put("CA", caWeeklyData);
    
    System.out.println(weeklyData);

    // Convert to monthly data
    Map<String, Map<String, Object>> monthlyData = convertToMonthlyData(weeklyData);

    System.out.println(monthlyData);

      }
    
    private static Map<String, Object> createWeeklyObject(int productSales, int noOfUnitsSold,      int customerViews, double conversion) {
    Map<String, Object> weeklyObject = new HashMap<>();
    weeklyObject.put("productSales", productSales);
    weeklyObject.put("noOfUnitsSold", noOfUnitsSold);
    weeklyObject.put("customerViews", customerViews);
    weeklyObject.put("conversion", conversion);
    return weeklyObject;
      }
    }

I don't have much hand's on experience using lambda and streams for which I think using these concepts, this can be made even more efficiently. Can you please guide me on this one?

Thanks in advance


Solution

  • Using Java Streams you can refer below code

    private static Map<String, Map<String, Map<String, Object>>> convertToMonthlyData(Map<String, Map<String, Object>> weeklyData) {
        DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
    
        Map<String, Map<String, Map<String, Object>>> monthwiseMap = new HashMap<>();
    
        weeklyData.forEach((country, weeklyDataDetail) -> {
            Map<String, Map<String, Object>> countryMonthwiseMap = weeklyDataDetail.entrySet().stream()
                    .collect(Collectors.groupingBy(entry -> YearMonth.parse(entry.getKey(), weekFormatter).format(monthFormatter),
                            Collectors.mapping(Map.Entry::getValue, Collectors.toList())))
                    .entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> combineWeeklyData(entry.getValue())));
    
            monthwiseMap.put(country, countryMonthwiseMap);
        });
    
        return monthwiseMap;
    
    }
    
    private static Map<String, Object> combineWeeklyData(List<Object> weeklyData) {
        Map<String, Object> combinedData = new HashMap<>();
        int totalCustomerViews = 0;
        int totalProductSales = 0;
        int totalNoOfUnitsSold = 0;
        double totalConversion = 0.0;
    
        for (Object data : weeklyData) {
            if (data instanceof Map) {
                Map<String, Object> weeklyDataMap = (Map<String, Object>) data;
                totalCustomerViews += (int) weeklyDataMap.get("customerViews");
                totalProductSales += (int) weeklyDataMap.get("productSales");
                totalNoOfUnitsSold += (int) weeklyDataMap.get("noOfUnitsSold");
                totalConversion += (double) weeklyDataMap.get("conversion");
            }
        }
    
        int numWeeks = weeklyData.size();
        double averageConversion = (numWeeks > 0) ? totalConversion / numWeeks : 0.0;
    
        combinedData.put("customerViews", totalCustomerViews);
        combinedData.put("productSales", totalProductSales);
        combinedData.put("noOfUnitsSold", totalNoOfUnitsSold);
        combinedData.put("conversion", averageConversion);
    
        return combinedData;
    }