4

I have a class

public class Person {

    private String name;
    private String country;
    private String city;
    private String pet;
    private int totalCountryToCityCount;
    private int petCount;

    public Person(String name, String country, String city, String pet, int total, int petCount) {
        this.name = name;
        this.country = country;
        this.city = city;
        this.pet = pet;
        this.totalCountryToCityCount = total;
        this.petCount = petCount;
    }

    public String getName() {
        return name;
    }

    public String getCountry() {
        return country;
    }

    public String getCity() {
        return city;
    }

    public String getPet() {
        return pet;
    }

    public int getPetCount() {
        return petCount;
    }

    public int getTotalCountryToCityCount() {
        return totalCountryToCityCount;

    }

}

and Given a list of Person class, I have do aggregations based upon the different properties of the class. For eg -

Person person1 = new Person("John", "USA", "NYC", "Max", 1, 2);
        Person person2 = new Person("Steve", "UK", "London", "Lucy", 2, 8);
        Person person3 = new Person("Anna", "USA", "NYC", "Max", 4, 32);
        Person person4 = new Person("Mike", "USA", "Chicago", "Duke", 5, 1);
        Person person5 = new Person("Test", "INDIA", "HYD", "Tommy", 4, 32);
        Person person6 = new Person("Test1", "INDIA", "HYD", "Tommy", 4, 65);
        Person person7 = new Person("Tim", "USA", "Chicago", "Duke", 5, 111);
        Person person8 = new Person("Tim", "USA", "Chicago", "Puke", 5, 111);
        Person person9 = new Person("Test1", "INDIA", "DELHI", "Tommy", 4, 65);
List<Person> persons = Arrays
            .asList(person1, person2, person3, person4, person5, person6, person7, person8,
                person9);

Now I need to get a result such that I should get the total "totalCountryToCityCount" based upon the combinations of country and city and I should get total "petCount" based upon combinations of country,city and pet. I am able to get them separately using groupingBy and summingint

private Map<String, Map<String, Integer>> getTotalCountForCountry(List<Person> persons) {
        return persons.stream().collect(groupingBy(Person::getCountry, getCityCount()));
    }

    public Collector<Person, ?, Map<String, Integer>> getCityCount() {
        return groupingBy(Person::getCity, summingInt(Person::getTotal));
    }

    public Map<String, Map<String, Map<String, Integer>>> threeLevelGrouping(List<Person> persons) {
       return  persons
            .stream().collect(
                groupingBy(Person::getCountry, groupByCityAndPetName()
                )
            );
    }

    private Collector<Person, ?, Map<String, Map<String, Integer>>> groupByCityAndPetName() {
        return groupingBy(Person::getCity, groupByPetName());
    }

    private Collector<Person, ?, Map<String, Integer>> groupByPetName() {
        return groupingBy(Person::getPet, summingInt(Person::getPetCount));
    }

which gives the result

{USA={Chicago={Puke=111, Duke=112}, NYC={Max=34}}, UK={London={Lucy=8}}, INDIA={DELHI={Tommy=65}, HYD={Tommy=97}}}
{USA={Chicago=15, NYC=5}, UK={London=2}, INDIA={DELHI=4, HYD=8}}

but the actual result which I want is :-

{USA={Chicago={15,{Puke=111, Duke=112}}, NYC={5,{Max=34} }, UK={London={2, {Lucy=8}}, INDIA={DELHI={4, {Tommy=65}}, , HYD={8,{Tommy=97}}}}

is there a way to achieve the same using Java stream API

I also tried using the code -

 personList.stream().collect(groupingBy(person -> person.getCountry(), collectingAndThen(reducing(
            (a, b) -> new Person(a.getName(), a.getCountry(), a.getCity(), a.getPet(),
                a.getTotal() + b.getTotal(), a.getPetCount() + b.getPetCount())),
            Optional::get)))
            .forEach((country, person) -> System.out.println(country + person));

But was getting the result -

USAPerson{name='John', country='USA', city='NYC'}
UKPerson{name='Steve', country='UK', city='London'}
INDIAPerson{name='Test', country='INDIA', city='HYD'}

with the counts surprisingly removed

Ashish Saraswat
  • 239
  • 1
  • 2
  • 5
  • But `15` & `{Puke=111, Duke=112}` are not of the same type. What result type do you await? – Yassin Hajaj Jul 11 '19 at 14:05
  • i need the aggregation (total sum of ) total pet count based upon country , city and petCount and another sum(totalCountryToCityCount) based upon country and city . Was able to get by using two different collectors which i have added in the code section here . But can it be done in one iterations – Ashish Saraswat Jul 11 '19 at 14:19
  • I get it, but `15` is `int` and `{Puke=111, Duke=112}` is `Map`... So your result would be something like `Map>`.. Is that OK? – Yassin Hajaj Jul 11 '19 at 14:25
  • Yes that will be fine as long as i may get the required result – Ashish Saraswat Jul 11 '19 at 14:29
  • `Person` does not compile, btw. and _don't_ use `new Integer(...)` – Eugene Jul 11 '19 at 18:48
  • I think you need an object `SomeObject { int totalCount; Map map; }` for this purpose. Keeping your structure flat will be a nightmare – Yassin Hajaj Jul 12 '19 at 09:28
  • @AshishSaraswat is this what you were looking for btw? – Eugene Jul 18 '19 at 12:30

1 Answers1

1

What you are looking for really is Collectors::teeing, but only available in java-12:

System.out.println(
        persons.stream()
               .collect(Collectors.groupingBy(
                   Person::getCountry,
                   Collectors.groupingBy(
                       Person::getCity,
                       Collectors.teeing(
                           Collectors.summingInt(Person::getTotalCountryToCityCount),
                           Collectors.groupingBy(
                               Person::getPet,
                               Collectors.summingInt(Person::getPetCount)
                           ),
                           SimpleEntry::new
                       )
                   ))));

A back-port for java-8 it is available here.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • @YassinHajaj `{USA={Chicago=15={Puke=111, Duke=112}, NYC=5={Max=34}}, UK={London=2={Lucy=8}}, INDIA={DELHI=4={Tommy=65}, HYD=8={Tommy=97}}}` – Eugene Jul 12 '19 at 09:30
  • A bit different from what OP is asking but let's hope he accepts this :) `{USA={Chicago={15,{Puke=111, Duke=112}}, NYC={5,{Max=34} }, UK={London={2, {Lucy=8}}, INDIA={DELHI={4, {Tommy=65}}, , HYD={8,{Tommy=97}}}} ` – Yassin Hajaj Jul 12 '19 at 09:32
  • 1
    @YassinHajaj this is _simply_ under a different format, because of `SimplerEntry::new`, it can be done via `(left, right) -> left + "," + right` instead. – Eugene Jul 12 '19 at 09:36