Группируйте и сортируйте по нескольким атрибутам с помощью stream: Java 8

#java #collections #java-8 #java-stream

#java #Коллекции #java-8 #java-stream

Вопрос:

У меня есть список основных объектов

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

и у меня есть CompanyEntity

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

Я пытаюсь преобразовать свой список в, Map<String,List<CompanyEntity>> где key будет keyword и List<CompanyEntity> будет иметь среднее значение всех затрат и также отсортирован. Я пытаюсь сделать это в stream и Java 8.

Для определенного ключевого слова в качестве входных данных я делаю это.

 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()));
    }
  

Но я пытаюсь создать карту для всех ключевых слов. Возможно ли или имеет смысл повторить весь список заново?
В настоящее время keyowordMap имеет тип Map<String,MainEntity> , который я сделал, повторив список MainEntity , но я хочу Map<String,List<MainEntity>> .

Ответ №1:

Сначала создайте keyWordMap

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

Затем повторите отображение, для каждого ключевого слова вы можете напрямую получить список CompanyEntity сортировки по среднему значению и использования map() для преобразования данных и сбора в виде списка, затем поместите в 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);
}
  

Или вы хотите сделать это одним выстрелом

 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())));
  

Комментарии:

1. Я пытаюсь создать Map<Keyword, List<CompanyEntity>> , а не для одного ключевого слова.

2. @AlokShukla вы показываете код только для одного конкретного ключевого слова, и у вас уже есть keyWordMap право? Вам также нужен список MainEntity to keyWordMap ?

3. То, что у меня есть, MainEntity("keyword1",1,"company 1"); MainEntity("keyword2",1,"company 1"); MainEntity("keyword1",1,"company 2"); MainEntity("keyword2",1,"company 2"); что я сделал в keyWord map, было просто итерацией по списку, который не агрегируется. Это похоже, Map<String, MainEntity> но я пытаюсь сделать Map<String, List<Entity>> . Также отредактирую сообщение, чтобы сделать его более понятным

4. @AlokShukla Также добавлено одноразовое решение.

5. И вам нужно повторить список еще раз, просто повторите map и получите список объектов для ключевого слова и преобразуйте в список CompanyEntity

Ответ №2:

Другой ответ полностью изменил свой ответ после первоначального неправильного понимания вопроса, и в хорошем настроении StackOverflow он привлек первое одобрение, поэтому теперь принят и получил наибольшее количество голосов. Но в этом коде есть еще несколько шагов, показывающих, что происходит:

Это должно дать вам результат:

 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;
    }


}
  

Вывод: {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)])}

Возможно, вам удастся пропустить некоторые шаги, это просто быстро вводится. Вы, конечно, можете встроить материал, чтобы он выглядел намного короче / чище, но этот формат показывает, что происходит.