我有以下课程:
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;
}
}
这是一种使用流来完成此操作的有趣方法..
这里我使用
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;
}
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
最高的科目。