使用流进行多属性分组和排序:Java 8

huangapple go评论73阅读模式
英文:

Group and sort by multiple attribute using stream: Java 8

问题

我有一个MainEntity的列表

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>>,其中键将是keyword,而List<CompanyEntity>将具有所有成本的平均值并进行排序。我试图在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(Collectors.groupingBy(MainEntity::getCompany,
            Collectors.averagingDouble(MainEntity::getCost)));
result.put(keyword, new ArrayList<>());

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

但是我想为所有关键词创建一个映射。这是可能的吗?还是再次迭代整个列表有意义?当前keyWordMap的类型是Map<String, MainEntity>,我是通过迭代MainEntity列表来实现的,但我想要Map<String, List<MainEntity>>

英文:

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&lt;String,List&lt;CompanyEntity&gt;&gt; where key will be keyword and List&lt;CompanyEntity&gt; 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&lt;MainEntity&gt; entityList = keyWordMap.get(entity.getKeyword());
        entityList.add(entity);
        keyWordMap.put(entity.getKeyword(), entityList);

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

    for (Map.Entry&lt;String, Double&gt; 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&lt;String,MainEntity&gt; which I did by iterating list of MainEntity, but I want Map&lt;String,List&lt;MainEntity&gt;&gt;.

答案1

得分: 3

首先,创建一个 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())));
英文:

First, make a keyWordMap

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

Or you want to do this in one-shot

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

答案2

得分: 1

以下是翻译好的代码部分:

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 {
        // 省略部分,具体内容请查看原文
    }

    //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 {
        // 省略部分,具体内容请查看原文
    }

    @Value
    public static class CompanyEntity {
        // 省略部分,具体内容请查看原文
    }

    @Value
    public static class CompanyEntityListWithStats {
        // 省略部分,具体内容请查看原文
    }
}

输出结果:{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)])}

英文:

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(&quot;key1&quot;, 10D, &quot;company1&quot;);
        MainEntity two = new MainEntity(&quot;key2&quot;, 5D, &quot;company2&quot;);
        MainEntity three = new MainEntity(&quot;key1&quot;, 7D, &quot;company3&quot;);
        MainEntity four = new MainEntity(&quot;key2&quot;, 3D, &quot;company4&quot;);
        List&lt;MainEntity&gt; mainEntityList = List.of(one, two, three, four);

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

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

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

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

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

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

    //map MainEntity to CompanyEntity
    private static Function&lt;MainEntity, CompanyEntity&gt; getCompanyListFunction() {
        return mainEntity -&gt; 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&lt;CompanyEntity&gt; 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.

huangapple
  • 本文由 发表于 2020年8月15日 16:06:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63423924.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定