0

I would like to group a list of maps by value and insert(merge) as grouped lists in certain conditions. Group by a value "yr" and merge "tag" by the same "category" then sum up the "price" them together, then after order in descending order by "price"

List<Grocery> groceryList = new ArrayList<>();

Grocery grocery = new Grocery();

List<HahsMap<String, Object>> tagList = new ArrayList<>();

HashMap<String, Object> tagMap = new HashMap<>();

## feeding data example
tagMap.put("category", "A");
tagMap.put("name", "Apple");
tagMap.put("price", 10);

tagList.add(tagMap);


grocery.setYr(String yr);
grocery.setTag(List<Hashmap<String, Object>> tagList);

groceryList.add(grocery);

This is an example of a list of maps.

List<Grocery> groceryList = 

[
  {
    "yr": "2021",
    "tag": [
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 10
      }
    ]
  },
  {
    "yr": "2021",
    "tag": [
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 10
      }
    ]
  },
  {
    "yr": "2021",
    "tag": [
      {
        "Category": "B",
        "Name": "Banana",
        "Price": 5
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 10
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "B",
        "Name": "Banana",
        "Price": 30
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "C",
        "Name": "Candy",
        "Price": 10
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "C",
        "Name": "Candy",
        "Price": 30
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 10
      }
    ]
  }
]

This is the sample output I want to get

List<Grocery> result =

[
  {
    "yr": "2021",
    "tag": [
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 20
      },
      {
        "Category": "B",
        "Name": "Banada",
        "Price": 5
      }
    ]
  },
  {
    "yr": "2020",
    "tag": [
      {
        "Category": "C",
        "Name": "Candy",
        "Price": 40
      },
      {
        "Category": "B",
        "Name": "Banada",
        "Price": 30
      },
      {
        "Category": "A",
        "Name": "Apple",
        "Price": 20
      }
    ]
  }
]

I know this is tricky to combine all the condition into one, could anyone help me out?? It will be really appreciated if at least I could have some ideas what to do about this..

I tried to group them in a 'yr' first, and try to find the values to map out then sort them out with the same 'category' then sum up.. but i have no clue on sorting them and merge them together with summed up 'price'..

Thanks in advance!!

  • Please, provide corresponding classes. – Monsieur Merso Jul 06 '21 at 06:50
  • What did you try so far? If you already have succeeded in grouping by year I assume you already have a `List` for each year. Now you could create an intermediate grouping using a `TreeMap>` where the key is the category. Once that's done you'd just need to iterate over the tree map's elements as they are already sorted, sum up the prices in each list and create a single `Grocery` element using the category (and category name) as well as the summed up price. Put those back into the list for each year (after clearing that list of course). – Thomas Jul 06 '21 at 06:52
  • I ve updated the class `Grocery` that I came up with, thanks all of you for advising me. I tried to map them out by `key(String yr)` and `value(Hashmap tag)` but it seems not working on my side, I guess I did something wrong on grouping them.. – steamworks99 Jul 06 '21 at 07:17

1 Answers1

0

Please check If this is what you are looking for.

String inputJson = "[{\"yr\":\"2021\",\"tag\":[{\"Category\":\"A\",\"Name\":\"Apple\",\"Price\":10}]},{\"yr\":\"2021\",\"tag\":[{\"Category\":\"A\",\"Name\":\"Apple\",\"Price\":10}]},{\"yr\":\"2021\",\"tag\":[{\"Category\":\"B\",\"Name\":\"Banana\",\"Price\":5}]},{\"yr\":\"2020\",\"tag\":[{\"Category\":\"A\",\"Name\":\"Apple\",\"Price\":10}]},{\"yr\":\"2020\",\"tag\":[{\"Category\":\"B\",\"Name\":\"Banana\",\"Price\":30}]},{\"yr\":\"2020\",\"tag\":[{\"Category\":\"C\",\"Name\":\"Candy\",\"Price\":10}]},{\"yr\":\"2020\",\"tag\":[{\"Category\":\"C\",\"Name\":\"Candy\",\"Price\":30}]},{\"yr\":\"2020\",\"tag\":[{\"Category\":\"A\",\"Name\":\"Apple\",\"Price\":10}]}]";
    ObjectMapper objectMapper = new ObjectMapper();

    //convert json to input Grocery List using jackson object mapper
    List<Grocery> groceries = objectMapper.readValue(inputJson, new TypeReference<List<Grocery>>() {
    });

    List<Grocery> output = new ArrayList<>();
    //group all groceries by year
    Map<String, List<Grocery>> groceriesByYear = groceries.stream().collect(Collectors.groupingBy(grocery -> grocery.getYr()));

    groceriesByYear.keySet().forEach(year -> {
        //for each year, get the grocery list
        List<Grocery> groceryList = groceriesByYear.get(year);
        //merge tags which have same category
        Map<String, Tag> tagsPerCategory = groceryList.stream().flatMap(grocery -> grocery.getTag().stream()).collect(Collectors.toMap(Tag::getCategory, Function.identity(),
                (tag1, tag2) -> {
                    //merge function, sum up the price of tag which has same category
                    tag1.setPrice(tag1.getPrice() + tag2.getPrice());
                    return tag1;
                }));
        //create new grocery object with required data
        Grocery grocery = new Grocery();
        grocery.setYr(year);
        grocery.setTag(new ArrayList<>(tagsPerCategory.values()));
        output.add(grocery);
    });
    //output list will have groceries grouped by year and tag with summed up price

Edit

You can do filter on tag stream to avoid null tag objects and add not null check if you think price can be null or you can define a method to provide default value if price is not already set like below

public static <T> T getValueOrDefault(T value, T defaultValue) {
    return value != null ? value : defaultValue;
}

and your tagsPerCategory looks like below

Map<String, Tag> tagsPerCategory = groceryList.stream().flatMap(grocery -> grocery.getTag().stream()).filter(tag -> tag != null && tag.getPrice() != null)
                .collect(Collectors.toMap(Tag::getCategory, Function.identity(),
                        (tag1, tag2) -> {
                            //sum up the price of tag which has same category
                            tag1.setPrice(getValueOrDefault(tag1.getPrice(), 0) + getValueOrDefault(tag2.getPrice(), 0));
                            return tag1;
                        }));
Pramod
  • 787
  • 4
  • 19
  • I really appreciate your efforts in answering my questions! I do really appreciate it!! I just tried to implement the code based on ur answer, but just wondering how could I avoid the null pointer exception error on `tag1.setPrice(tag1.getPrice() + tag2.getPrice());` I think I need to arrange the those entities that involved and make sure everything should be fit inside your answer? This means I can't set the price in the Tag entity right? – steamworks99 Jul 06 '21 at 08:35
  • Thank you so much for your assistance, you are a big genius!! i will try to implement it based on ur answer, and give you an update!! i wish I could buy you coffee or lunch to learn Java from you!! I really do appreciate it! – steamworks99 Jul 06 '21 at 09:02
  • Happy to help :) Please accept this answer If you are satisfied. Thanks – Pramod Jul 06 '21 at 09:39
  • It works very very charm!!! Thank you so much, Pramod!! You are the best in the world!! :) – steamworks99 Jul 06 '21 at 19:46