我正在熟悉流API,并面临一些问题。
这是我的代码。
class Student {
String name;
int height;
LocalDate dob;
Map<String, Integer> scores;
public Student(String name, int height, LocalDate dob, Map<String, Integer> scores) {
this.name = name;
this.height = height;
this.dob = dob;
this.scores = scores;
}
// getters, setters and toString ...
}
public static void main(String [] args) {
LocalDate dob1 = LocalDate.of(2000, 1, 1);
LocalDate dob2 = LocalDate.of(2001, 2, 10);
LocalDate dob3 = LocalDate.of(2001, 3, 15);
LocalDate dob4 = LocalDate.of(2002, 4, 18);
LocalDate dob5 = LocalDate.of(2002, 5, 19);
Map<String, Integer> scores1 = new HashMap<String, Integer>();
scores1.put("Math", 100);
scores1.put("Physics", 95);
scores1.put("Chemistry", 90);
scores1.put("Biology", 100);
Map<String, Integer> scores2 = new HashMap<String, Integer>();
scores2.put("Math", 90);
scores2.put("Physics", 55);
scores2.put("Chemistry", 95);
scores2.put("Biology", 85);
Map<String, Integer> scores3 = new HashMap<String, Integer>();
scores3.put("Math", 85);
scores3.put("Physics", 50);
scores3.put("Chemistry", 100);
scores3.put("Biology", 75);
Map<String, Integer> scores4 = new HashMap<String, Integer>();
scores4.put("Math", 50);
scores4.put("Physics", 45);
scores4.put("Chemistry", 88);
scores4.put("Biology", 40);
Map<String, Integer> scores5 = new HashMap<String, Integer>();
scores5.put("Math", 65);
scores5.put("Physics", 100);
scores5.put("Chemistry", 88);
scores5.put("Biology", 55);
Student s1 = new Student("Tom", 6, dob1, scores1);
Student s2 = new Student("Dan", 7, dob2, scores2);
Student s3 = new Student("Ron", 5, dob3, scores3);
Student s4 = new Student("Pete", 5, dob4, scores4);
Student s5 = new Student("Sam", 6, dob5, scores5);
List<Student> students = new ArrayList<Student>();
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
}
从上面的List<Student>
,我试图生成和打印一个地图Map<String, <Map<String, Integer>>
包含学生与最高分在每个主题格式<subject_name, <student_name, Score>>
到目前为止,我想出了一个如下所示的解决方案。
public static void printStudentWithHighestScoreInEachSubject(List<Student> S) {
HashMap<String, Map<String, Integer>> map = new HashMap<String, Map<String, Integer>>();
List<String> sub = S.get(0).getScores().keySet().stream().collect(Collectors.toList());
for (String subj : sub) {
HashMap<String, Integer> t = new HashMap<String,Integer>();
for (Student s: S) {
Integer score = s.getScores().get(subj);
t.put(s.getName(), score);
}
map.put(subj, t);
}
HashMap<String, Map.Entry<String, Integer>> res = (HashMap<String, Map.Entry<String, Integer>>) map.entrySet().stream().collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue().entrySet().stream().max((x1, x2) -> x1.getValue().compareTo(x2.getValue())).get()));
System.out.println(res);
}
我正试图想出一个解决方案,不使用for
循环和处理一切使用流API。
流式处理您的学生列表,平面映射主题和映射到一个简单的条目,主题为键,学生为值,收集到映射使用主题为键和二进制操作符,以获得学生的最大得分为关键主题,包装这个收集器在一个collector。
public static void printStudentWithHighestScoreInEachSubject(List<Student> students) {
Map<String, Map<String, Integer>> result =
students.stream()
.flatMap(student -> student.getScores().keySet().stream()
.map(subject -> new SimpleEntry<>(subject, student)))
.collect(
Collectors.collectingAndThen(
Collectors.toMap(Entry::getKey, Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(e -> e.getValue().getScores().get(e.getKey())))),
map -> map.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
e -> Map.of(e.getValue().getValue().getName(), e.getValue().getValue().getScores().get(e.getKey()))))
));
System.out.println(result);
}
这可以使用collect()
的风格,它期望三个参数(供应商,累加器和组合器)和Java 8方法Map.merge()
:
public static void printStudentWithHighestScoreInEachSubject(List<Student> students) {
Map<String, Map.Entry<String, Integer>> bestStudentsScoreBySubject = students.stream()
.collect(HashMap::new,
(Map<String, Map.Entry<String, Integer>> map, Student student) ->
student.getScores().forEach((k, v) -> map.merge(k, Map.entry(student.getName(), v),
(oldV, newV) -> oldV.getValue() < v ? newV : oldV)),
(left, right) -> right.forEach((k, v) -> left.merge(k, v,
(oldV, newV) -> oldV.getValue() < newV.getValue() ? newV : oldV)));
bestStudentsScoreBySubject.forEach((k, v) -> System.out.println(k + " : " + v));
}
代替你在累加器和组合器中看到的相同的lambda表达式,我们可以在方法中引入BiFunction
类型的局部变量并通过其名称引用它,或者我们使用静态方法BinaryOperator.maxBy()
,它是自解释的,比包含三元操作符的lambda更具可读性。
方法maxBy()
需要一个比较器,它可以使用Java 8静态方法Map.Entry.comparingByValue()
定义:
BinaryOperator.maxBy(Map.Entry.comparingByValue())
上面的代码可以这样写:
public static void printStudentWithHighestScoreInEachSubject(List<Student> students) {
Map<String, Map.Entry<String, Integer>> bestStudentsScoreBySubject = students.stream()
.collect(HashMap::new,
(Map<String, Map.Entry<String, Integer>> map, Student student) ->
student.getScores().forEach((k, v) -> map.merge(k, Map.entry(student.getName(), v),
BinaryOperator.maxBy(Map.Entry.comparingByValue()))),
(left, right) -> right.forEach((k, v) -> left.merge(k, v,
BinaryOperator.maxBy(Map.Entry.comparingByValue()))));
bestStudentsScoreBySubject.forEach((k, v) -> System.out.println(k + " : " + v));
}
输出(对于问题中提供的数据样本):
Chemistry : Ron=100
Biology : Tom=100
Math : Tom=100
Physics : Sam=100
你可以使用这个在线演示。
旁注:
- 遵循Java命名约定,避免使用像
S
这样的名字——它不是很有描述性,参数名应该以小写字母开头。您可能会考虑引入一个实用程序类,它将提供对主题列表的方便访问,而不是从随机学生的属性中提取。定义一个类(record)负责存储像subject
+student's name
+score
和subject
+score
这样的数据集可能是有意义的。它将允许您有效地操作数据,通过减少代码复杂性并使其更有意义(假设这些记录和属性将具有有意义的名称)。
// Group by subject then score, get the sorted TreeMap
Map<String, TreeMap<Integer, List<String>>> map = students.stream()
.flatMap(stu -> stu.getScores().entrySet().stream().map(entry -> entry.getKey() + "-" + entry.getValue() + "-" + stu.getName()))
.collect(Collectors.groupingBy(info -> info.split("-")[0], Collectors.groupingBy(info -> Integer.valueOf(info.split("-")[1]), TreeMap::new, Collectors.toList())));
//Map score names to name score pairs
Map<String, Map<String, Integer>> result = map.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
entry -> {
Map.Entry<Integer, List<String>> scoreNames = entry.getValue().lastEntry();
return scoreNames.getValue().stream().collect(Collectors.toMap(info -> info.split("-")[2], name -> scoreNames.getKey()));
}
));