Alok Shukla
Alok Shukla

Reputation: 107

Group and sort by multiple attribute using stream: Java 8

I have List of MainEntity

public class MainEntity {
private String keyword;
private double cost;
private String company;
}

and I have CompanyEntity

public class CompanyEntity {
    private double cost;
    private String company;
    }

I am trying to transform my list into Map<String,List<CompanyEntity>> where key will be keyword and List<CompanyEntity> will have average of all the costs and sorted too. I am trying to do it in stream and Java 8.

For a particular keyword as input I am doing this.

List<MainEntity> entityList = keyWordMap.get(entity.getKeyword());
        entityList.add(entity);
        keyWordMap.put(entity.getKeyword(), entityList);

Map<String, Double> average = (keyWordMap.get(keyword)).stream()
            .collect(groupingBy(MainEntity::getCompany,
                    Collectors.averagingDouble(MainEntity::getCtr)));
    result.put(keyword, new ArrayList<>());

    for (Map.Entry<String, Double> entity : average.entrySet()) {
        result.get(keyword).add(new CompanyEntity(entity.getKey(), entity.getValue()));
    }

But I trying to create a map for all keywords. Is is possible or iterating whole list again makes sense? Currently keyowordMap is of type Map<String,MainEntity> which I did by iterating list of MainEntity, but I want Map<String,List<MainEntity>>.

Upvotes: 2

Views: 274

Answers (2)

Sebastiaan van den Broek
Sebastiaan van den Broek

Reputation: 6331

The other answer completely changed its answer after initially misunderstanding the question and in good StackOverflow spirits it attracted the first upvote so is now accepted and highest upvoted. But this has a few more steps in the code showing what's happening:

This should get you the result:

import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Value;

public class CompanyEntityStackOverflowQuestion {

    public static void main(String[] args) throws IOException {

        //setup test data
        MainEntity one = new MainEntity("key1", 10D, "company1");
        MainEntity two = new MainEntity("key2", 5D, "company2");
        MainEntity three = new MainEntity("key1", 7D, "company3");
        MainEntity four = new MainEntity("key2", 3D, "company4");
        List<MainEntity> mainEntityList = List.of(one, two, three, four);

        //group list by keyword
        Map<String, List<MainEntity>> mainEntityByKeyword = mainEntityList.stream()
            .collect(Collectors.groupingBy(MainEntity::getKeyword));

        //map to companyEntity object
        Stream<SimpleEntry<String, List<CompanyEntity>>> mapped = mainEntityByKeyword.entrySet().stream()
            .map(entry -> new SimpleEntry<>(entry.getKey(), entry.getValue().stream().map(
                getCompanyListFunction()).collect(Collectors.toList())));

        //sort and calculate average
        Stream<SimpleEntry<String, CompanyEntityListWithStats>> mappedToListWithStats = mapped
            .map(entry -> new SimpleEntry<>(entry.getKey(),
                new CompanyEntityListWithStats(entry.getValue().stream().mapToDouble(company -> company.cost).average().orElse(0D), //or use Collectors.averagingDouble(company -> company.cost))
                    sortList(entry.getValue()))));

        //collect back to map
        Map<String, CompanyEntityListWithStats> collect = mappedToListWithStats
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

        //show result
        System.out.println(collect);
    }

    //sort by cost
    private static List<CompanyEntity> sortList(List<CompanyEntity> list) {
        list.sort(Comparator.comparing(company -> company.cost));
        return list;
    }

    //map MainEntity to CompanyEntity
    private static Function<MainEntity, CompanyEntity> getCompanyListFunction() {
        return mainEntity -> new CompanyEntity(mainEntity.cost, mainEntity.company);
    }

    @Value
    public static class MainEntity {

        public String keyword;
        public double cost;
        public String company;
    }

    @Value
    public static class CompanyEntity {

        public double cost;
        public String company;
    }

    @Value
    public static class CompanyEntityListWithStats {

        public double average;
        public List<CompanyEntity> companyList;
    }


}

Output: {key1=CompanyEntityStackOverflowQuestion.CompanyEntityListWithStats(average=8.5, companyList=[CompanyEntityStackOverflowQuestion.CompanyEntity(cost=7.0, company=company3), CompanyEntityStackOverflowQuestion.CompanyEntity(cost=10.0, company=company1)]), key2=CompanyEntityStackOverflowQuestion.CompanyEntityListWithStats(average=4.0, companyList=[CompanyEntityStackOverflowQuestion.CompanyEntity(cost=3.0, company=company4), CompanyEntityStackOverflowQuestion.CompanyEntity(cost=5.0, company=company2)])}

You may be able to skip some steps, this is just quickly typed out. You can of course inline stuff to make it look a lot shorter/cleaner, but this format shows what's happening.

Upvotes: 1

Eklavya
Eklavya

Reputation: 18430

First, make a keyWordMap

Map<String, List<MainEntity>> keyWordMap = 
            mainEntityList
            .stream()
            .collect(Collectors.groupingBy(MainEntity::getKeyword));

Then iterate the map, for each keyword, you can directly get the list of CompanyEntity sort by average value and using map() to transform the data and collect as List, then put in result

Map<String,List<CompanyEntity>> result = ....
for (Map.Entry<String, List<MainEntity> entry : keyWordMap.entrySet()) {
    List<CompanyEntity> list = entry.getValue().stream()
                .collect(groupingBy(MainEntity::getCompany,
                        Collectors.averagingDouble(MainEntity::getCtr)))
                .entrySet()
                .stream()
                .sorted(Comparator.comparing(e -> e.getValue()))
                .map(e -> new CompanyEntity(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
    result.put(entry.getKey(), list);
}

Or you want to do this in one-shot

Map<String,List<CompanyEntity>> mapData = 
           mainEntityList
            .stream()
            .collect(Collectors.groupingBy(MainEntity::getKeyWord,
                     Collectors.groupingBy(MainEntity::getCtr,
                        Collectors.averagingDouble(MainEntity::getCtr))))
            .entrySet()
            .stream()
            .collect(Collectors.toMap(m -> m.getKey(),
                 m -> m.entrySet()
                       .stream()
                       .sorted(Comparator.comparing(e -> e.getValue()))
                       .map(e -> new CompanyEntity(e.getKey(), e.getValue()))
                       .collect(Collectors.toList())));

Upvotes: 3

Related Questions