按最大值对 Java 集合中的元素进行分组、合并和去重

问题描述 投票:0回答:2

我有以下课程:

public class Student {  
    String studentId;
    List<Subject> subjects;
    //+Getters, setters, constructors
}

public class Subject {  
    int subjectId;
    String grade;   
    int marks;
    //+Getters, setters, constructors
}

输入示例:

Student S1 subjects=[1, A, 90], [2, B, 80], [2, C, 70]
Student S1 subjects=[2, A+, 95], [3,C,70]
Student S2 subjects=[1, D, 50]
Student S2 subjects=[1, D, 50]

输出示例:

Student S1 subjects=[1, A, 90], [2, A+, 95], [3,C,70]  //Subject 2 is selected based on highest marks
Student S2 subjects=[1, D, 50] //Avoided duplicate for same mark

我想实现一个合并和重复数据删除功能。 该函数应该只返回一个学生 ID 一次。

每个学生 ID 应包含仅在该学生中出现过一次的每个科目 ID。应根据最高分选择主题 ID。

public List<Student> consolidate (List<Student> students)
{
    List<Student> consolidatedStudents = new ArrayList<Student>();
    //???
    return consolidatedStudents;
}

我怎样才能以最有效的方式实现这一目标?

用于测试的示例主类

public class Main {
    
    public static void main(String args[])
    {
        List<Subject> s1Subjects_1 = new ArrayList<>();
        List<Subject> s1Subjects_2 = new ArrayList<>();
        Subject s1Physics = new Subject(1,"A",90);
        Subject s1Chemistry_1 = new Subject(2,"B",80);      
        Subject s1Chemistry_2 = new Subject(2,"C",70);
        Subject s1Chemistry_3 = new Subject(2,"A+",95);
        Subject s1Biology = new Subject(3,"C",70);
        
        s1Subjects_1.add(s1Physics);
        s1Subjects_1.add(s1Chemistry_1);
        s1Subjects_1.add(s1Chemistry_2);
        s1Subjects_2.add(s1Chemistry_3);
        s1Subjects_2.add(s1Biology);
        
        List<Subject> s2Subjects_1 = new ArrayList<>();
        List<Subject> s2Subjects_2 = new ArrayList<>();
        Subject s2Physics_1 = new Subject(1,"D",50);
        Subject s2Physics_2 = new Subject(1,"D",50);
        s2Subjects_1.add(s2Physics_1);
        s2Subjects_2.add(s2Physics_2);
        
        Student s1_1 = new Student("s1", s1Subjects_1);
        Student s1_2 = new Student("s1", s1Subjects_2);
        Student s2_1 = new Student("s2", s2Subjects_1);
        Student s2_2 = new Student("s2", s2Subjects_2);
        
        List<Student> input = new ArrayList<>();
        input.add(s1_1);
        input.add(s1_2);
        input.add(s2_1);
        input.add(s2_2);        
        
        List<Student> output = consolidate(input);
    }
    
    public static List<Student> consolidate (List<Student> students)
    {
        List<Student> consolidatedStudents = new ArrayList<Student>();
        //
        //???
        //
        return consolidatedStudents;
    }

}
java collections java-stream max
2个回答
1
投票

这是一种使用流来完成此操作的有趣方法..

这里我使用

Collectors.toMap
构建临时数据结构映射 将学生 ID 添加到科目列表中。我正在使用辅助合并功能合并主题列表。

合并功能根据标记选择最佳主题。

public static List<Student> consolidate(List<Student> students) {
    Map<String, List<Subject>> map = students.stream()
            .collect(Collectors.toMap(Student::getStudentId,
                    Student::getSubjects,
                    (subjects1, subjects2) -> merge(subjects1, subjects2)));
    return map.entrySet()
            .stream()
            .map(e -> new Student(e.getKey(), e.getValue()))
            .collect(Collectors.toList());
}

merge
函数为两个主题构建一个临时映射(主题ID ->主题的映射),并使用
Map#merge
将第二个合并到第一个中。对于具有相同 id 的主题,它会选择分数最高的一个。

private static List<Subject> merge(List<Subject> subjects1, List<Subject> subjects2) {
    Map<Integer, Subject> subjects1ById = new HashMap<>(subjectsMap(subjects1));
    Map<Integer, Subject> subjects2ById = subjectsMap(subjects2);
   
    subjects2ById.forEach((subId, sub) -> subjects1ById.merge(subId, sub,
            (sub1, sub2) -> pickBest(sub1, sub2)));
    return new ArrayList<>(subjects1ById.values());
}

private static Map<Integer, Subject> subjectsMap(List<Subject> subjects) {
    return subjects.stream()
            .collect(Collectors.toMap(Subject::getSubjectId, Function.identity(),
                    (sub1, sub2) -> pickBest(sub1, sub2)));
}

private static Subject pickBest(Subject s1, Subject s2) {
    return s1.getMarks() > s2.getMarks() ? s1 : s2;
}    

0
投票
private static List<Student> consolidate(List<Student> students) {
  Map<String, Student> studentsById = students.stream()
      .collect(Collectors.groupingBy(
          Student::getStudentId,
          Collectors.collectingAndThen(
              Collectors.toList(),
              l -> new Student(
                  l.getFirst().getStudentId(),
                  l.stream()
                      .flatMap(s -> s.getSubjects().stream())
                      .collect(Collectors.groupingBy(
                          Subject::getSubjectId,
                          Collectors.collectingAndThen(
                              Collectors.toList(),
                              l2 -> l2.stream().max(
                                  Comparator.comparing(Subject::getMarks)))))
                      .values().stream()
                      .map(Optional::orElseThrow)
                      .toList()))));
  return List.copyOf(studentsById.values());
}

这可能不是最容易遵循的,但它得到了预期的结果。

这按学生 ID 对学生进行分组,分组值是一个新的

Student
对象,该对象使用
studentId
以及从具有该 ID 的所有
Subject
对象中获取的
Student
对象列表。 该学生中的科目按
subjectId
减少为不同的科目,当有多个科目时,使用
marks
最高的科目。

© www.soinside.com 2019 - 2024. All rights reserved.