【问题标题】:How to map multiple properties under groupingBy property in java streams如何在java流中的groupingBy属性下映射多个属性
【发布时间】:2021-06-12 11:27:44
【问题描述】:

我正在尝试获取按部门分组的每个学生的总分数,并按他们的总分数排序。 这就是我正在尝试的方式。

学生班级属性:

private String firstName,lastName,branch,nationality,grade,shName;
private SubjectMarks subject;
private LocalDate dob;

SubjectMarks 类:

public SubjectMarks(int maths, int biology, int computers) {
    this.maths = maths;
    this.biology = biology;
    this.computers = computers;
}
public double getAverageMarks() {
    double avg = (getBiology() + getMaths() + getComputers())/3;
    return avg;
}

主类:

    Collections.sort(stList, new Comparator<Student>() {
        @Override
        public int compare(Student m1, Student m2) {
             if(m1.getSubject().getAverageMarks() == m2.getSubject().getAverageMarks()){
                    return 0;
                }
                return m1.getSubject().getAverageMarks()< m2.getSubject().getAverageMarks()? 1 : -1;
        }
    });

Map<String, List<Student>> groupSt=stList.stream().collect(Collectors.groupingBy(Student::getBranch,
        LinkedHashMap::new,Collectors.toList()));


groupSt.forEach((k, v) -> System.out.println("\nBranch Name: " + k + "\n" + v.stream()
.flatMap(stud->Stream.of(stud.getFirstName(),stud.getSubject().getAverageMarks())).collect(Collectors.toList())));

更新的代码:这就是我获取输出的方式。

  Branch Name: ECE
  [Bob, 96.0, TOM, 84.33333333333333]

  Branch Name: CSE
  [Karthik, 94.33333333333333, Angelina, 91.0, Arun, 80.66666666666667]

  Branch Name: EEE
  [Meghan, 85.0]

这是实际的排序顺序,但 Student 对象在一行中变平,由逗号 (,) 分隔。

在上面的输出中,由于 Bob 在所有分支中获得了最高的总分,因此 ECE 排在第一位,然后是其他分支,按学生总分排序。

预期结果是: 学生姓名列表及其总分已排序。

  Branch Name: ECE
  [{Bob, 96.0},{TOM, 84.33333333333333}]

  Branch Name: CSE
  [{Karthik, 94.33333333333333}, {Angelina, 91.0}, {Arun, 
   80.66666666666667}]

  Branch Name: EEE
  [Meghan, 85.0]

有没有办法使用流将名称和平均值映射到 groupingBy 属性上?

【问题讨论】:

  • 运行此代码时的实际输出是什么?最好也发布一下。
  • 那么你不想告诉我们的秘密是,你的代码会产生编译器错误?
  • 看看输出,你真的不需要一个内部的List&lt;Map&lt;String, Double&gt;,整体Map&lt;String, Map&lt;String, Double&gt;&gt;对你来说应该足够了。您可以考虑根据您想要的比较器为内部映射使用排序映射。
  • @Gautham M 抱歉,我很忙,无法回答您的问题。实际上,我的尝试有编译错误。
  • @Holger 没有秘密。我是java的初学者。我希望最好是提出建议而不是泄露秘密。谢谢

标签: java java-8 java-stream


【解决方案1】:

您可能更愿意选择返回类型为Map&lt;String, Map&lt;String, Double&gt;&gt; 或具有适当equalshashCode 的自定义类,以确保内部List&lt;Custom&gt; 之间的唯一性。我将基于前者构建解决方案,您可以将其转换为对您的实际代码更具可读性的解决方案。

将每个 branch 特定学生分组后,您可以采取的方法是使用 toMap 并根据基于的合并来确保 firstName 映射到该学生的最高平均分 Double::max... 然后根据标记(值)收集这些条目。

下面的代码可能看起来有点复杂,但也可以分解成几个步骤。

Map<String, LinkedHashMap<String, Double>> branchStudentsSortedOnMarks = stList.stream()
    .collect(Collectors.groupingBy(Student::getBranch, // you got it!
            Collectors.collectingAndThen(
                    Collectors.toMap(Student::getFirstName,
                            s -> s.getSubject().getAverageMarks(), Double::max), // max average marks per name
                    map -> map.entrySet().stream()
                            .sorted(Map.Entry.<String, Double>comparingByValue().reversed()) // sorted inn reverse based on value
                            .collect(Collectors.toMap(Map.Entry::getKey,
                                    Map.Entry::getValue, (a, b) -> b, LinkedHashMap::new)) 
            )));

【讨论】:

  • 谢谢,@Naman 我试过你的,这很酷。但它只对 LinkedHashMap 进行排序,我也在尝试对外部 Map 进行排序。我的意思是总分最高的学生所在的分支应该首先出现,然后是其他分支。我想我们可以先对列表进行排序。请在更新的问题中检查我的输出。再次感谢。
  • @Manmadh 这将是另一个按值排序的用例。否则是的,您也可以根据最大平均分数对初始列表进行排序,并在分组时将外部地图收集在 LinkedHashMap 中。
【解决方案2】:

首先,在您的Map&lt;String, List&lt;Map&lt;String,Double&gt;&gt;&gt; 列表中的映射将只包含一个键值对。所以我建议你返回Map&lt;String, List&lt;Entry&lt;String, Double&gt;&gt;&gt;。 (Entry in java.util.Map)

另外,在你的学生班级中创建一个getAverageMarks,它会返回:

return subject.getAverageMarks();
// First define a function to sort based on average marks
UnaryOperator<List<Entry<String, Double>>> sort = 
    list -> {
        Collections.sort(list, Collections.reverseOrder(Entry.comparingByValue()));
        return list;
    };

// function to create entry
Function<Student, Entry<String, Double>> getEntry = 
    s -> Map.entry(s.getFirstName(), s.getAverageMarks());

// return this
list.stream()
    .collect(Collectors.groupingBy(
        Student::getBranch,        
        Collectors.mapping(getEntry, // map each student
                           // collect and apply sort as finisher
                           Collector.of(ArrayList::new,
                                        List::add,
                                        (x,y) -> {x.addAll(y); return x;},
                                        sort))));            

【讨论】:

  • 最好代表Map&lt;String, List&lt;Entry&lt;String, Double&gt;&gt;&gt;as Map&lt;String, Map&lt;String, Double&gt;&gt;
  • @Naman 我最初想以 Map&lt;String, Map&lt;String, Double&gt;&gt; 的方式使用 TreeMap 作为内部地图。但是由于SortedMaps 采用了一个比较器,它按key 而不是value 排序,所以我想这样做。是否可以在收集到地图时进行排序?还是应该在收集地图后完成?
  • 请注意,从 Java 8 开始,您可以编写 list.sort(comparator) 而不是 Collections.sort(list, comparator)
  • @GauthamM 据我了解,除非您编写基于比较值的自定义 SortedMap 实现,否则您无法开箱即用。除此之外,我评论返回类型的主要意图是内部 List 允许基于名称的重复条目,否则将是内部映射的 key
  • @Naman 是的,现在知道了。我实际上没想到同一分支中的同一个人会有多组标记。
【解决方案3】:

输入:

List<Student> stList = Arrays.asList(
        new Student("John", "Wall", "A", "a", "C", "sa", new SubjectMarks(65, 67, 100), LocalDate.now()),
        new Student("Arun", "Wall", "B", "a", "C", "sa", new SubjectMarks(45, 61, 95), LocalDate.now()),
        new Student("Marry", "Wall", "A", "a", "C", "sa", new SubjectMarks(90, 80, 92), LocalDate.now())
);

想法:

  1. group by “分支”
  2. 对于每个组(列表) - sort 按年级和map 每个学生到“姓名”、“年级”的地图。

现在编码很容易:

Map<String, List<Map<String, Double>>> branchToSortedStudentsByGrade = stList.stream().collect(Collectors.groupingBy(
        Student::getBranch, Collectors.collectingAndThen(Collectors.toList(),
                l -> l.stream()
                        .sorted(Comparator.comparing(st -> st.getSubject().getAverageMarks(), Comparator.reverseOrder()))
                        .map(student -> Collections.singletonMap(student.getFirstName(), student.getSubject().getAverageMarks()))
                        .collect(Collectors.toList()))));

输出:

{
  A=[{Marry=87.0}, {John=77.0}],
  B=[{Arun=67.0}]
}

顺便说一句:

请注意,您在getAverageMarks 的浮点上下文中除以整数:

public double getAverageMarks() {
    double avg = (getBiology() + getMaths() + getComputers())/3;
    return avg;
}

这将导致所有成绩都采用这种格式-xx.0

如果是错误的,我会推荐这种方法:

public double getAverageMarks() {
    return DoubleStream.of(maths, biology, computers)
            .average()
            .getAsDouble();
}

【讨论】:

  • 谢谢你,@MostNeededRabit。它正在对学生列表进行排序。但是,当我将 John 添加到分支 C 时,分支 C 应该排在第一位,因为 Marry 与分支 C 的总分最高。这就是我获得输出的方式:分支名称:B [{Arun=67.0}] 分支名称:C [ {Marry=87.33333333333333},{John=77.33333333333333}] 预期为:分支名称:C [{Marry=87.33333333333333},{John=77.33333333333333}] 分支名称:B [{Arun=67.0}] 请检查我在更新后的输出问题。
  • @MostNeededRabit 感谢您使用 singletonMap() 的想法,这对我帮助很大。我修改了一些代码并得到了实际输出。
猜你喜欢
  • 1970-01-01
  • 2014-03-31
  • 1970-01-01
  • 2019-01-09
  • 2020-06-27
  • 2017-09-25
  • 1970-01-01
  • 2018-09-08
  • 2011-03-15
相关资源
最近更新 更多