使用Stream API更有效地解决编码任务?

etr*_*ric 7 java java-stream

我最近进行了技术访谈,并在Stream API上获得了小编码任务.
让我们考虑下一个输入:

public class Student {
    private String name;
    private List<String> subjects;
    //getters and setters
}

Student stud1 = new Student("John", Arrays.asList("Math", "Chemistry"));
Student stud2 = new Student("Peter", Arrays.asList("Math", "History"));
Student stud3 = new Student("Antony", Arrays.asList("Music", "History", "English"));

Stream<Student> studentStream = Stream.of(stud1, stud2, stud3);
Run Code Online (Sandbox Code Playgroud)

任务是使用Stream API查找具有独特主题的学生.
因此对于提供的输入预期结果(忽略顺序)是[John, Anthony].

我使用自定义收集器呈现了解决方案:

Collector<Student, Map<String, Set<String>>, List<String>> studentsCollector = Collector.of(
        HashMap::new,
        (container, student) -> student.getSubjects().forEach(
                subject -> container
                        .computeIfAbsent(subject, s -> new HashSet<>())
                        .add(student.getName())),
        (c1, c2) -> c1,
        container -> container.entrySet().stream()
                .filter(e -> e.getValue().size() == 1)
                .map(e -> e.getValue().iterator().next())
                .distinct()
                .collect(Collectors.toList())
);
List<String> studentNames = studentStream.collect(studentsCollector);
Run Code Online (Sandbox Code Playgroud)

但该解决方案被认为不是最佳/有效的.
您能否就这项任务更有效的解决方案分享您的想法?

更新:我从一个人那里得到另一个意见,他会使用reducer(Stream.reduce()方法).但我无法理解这会如何提高效率.你怎么看?

MC *_*ror 4

这是另一张。

\n\n
// using SimpleEntry from java.util.AbstractMap\nSet<Student> list = new HashSet<>(studentStream\n    .flatMap(student -> student.getSubjects().stream()\n        .map(subject -> new SimpleEntry<>(subject, student)))\n    .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)\n    .values());\nlist.remove(Student.SENTINEL_VALUE);\n
Run Code Online (Sandbox Code Playgroud)\n\n

(有意使用哨兵值,更多信息见下文。)

\n\n

步骤:

\n\n
    \n
  1. Set<Student> list = new HashSet<>(studentStream\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    我们正在从我们要收集的 Collection 中创建一个 HashSet。那是因为我们想摆脱重复的学生(具有多个独特科目的学生,在你的例子中是安东尼)。

  2. \n
  3. .flatMap(student -> student.subjects()\n    .map(subject -> new SimpleEntry(subject, student)))\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    我们将每个学生的科目平面映射到一个流中,但首先我们将每个元素映射到一对,以科目为键,以学生为值。这是因为我们需要保留主体和学生之间的关联。我正在使用AbstractMap.SimpleEntry,但是当然,您可以使用对的任何实现。

  4. \n
  5. .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    我们将这些值收集到地图中,将主题设置为键,将学生设置为结果地图的值。我们传入第三个参数 (a BinaryOperator) 来定义发生按键冲突时应该发生的情况。我们无法传入null,因此我们使用哨兵值1
    \n此时,我们通过将每个科目映射到一个学生(或者如果SENTINEL_VALUE一个科目有多个学生)来反转关系学生 \xe2\x86\x94 科目。

  6. \n
  7. .values());\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    我们获取地图的值,生成具有独特主题的所有学生的列表,加上哨兵值。

  8. \n
  9. list.remove(Student.SENTINEL_VALUE);\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    剩下要做的唯一一件事就是摆脱哨兵值。

  10. \n
\n\n
\n\n

1null在这种情况下我们不能使用。Map 的大多数实现不区分映射到该特定键的键null或不存在该特定键。或者,更准确地说,当重映射函数返回时,merge 方法会HashMap 主动删除节点null。如果我们想避免哨兵值,那么我们必须实现或拥有merge方法,可以像这样实现return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));

\n