使用Stream API编码任务的更有效解决方案?

时间:2019-02-12 14:22:35

标签: 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);

任务是使用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);

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

更新:我从另一个人那里得到另一种意见,即他将使用reducer(Stream.reduce()方法)。 但是我不知道这如何提高效率。你觉得呢?

4 个答案:

答案 0 :(得分:4)

这是另一个。

// using SimpleEntry from java.util.AbstractMap
Set<Student> list = new HashSet<>(studentStream
    .flatMap(student -> student.getSubjects().stream()
        .map(subject -> new SimpleEntry<>(subject, student)))
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
    .values());
list.remove(Student.SENTINEL_VALUE);

(有意使用前哨值,以下更多内容。)

步骤:

  1. Set<Student> list = new HashSet<>(studentStream
    

    我们正在从要收集的集合中创建一个HashSet。那是因为我们要摆脱重复的学生(在您的情况下,安东尼科有多个独特学科的学生)。

  2. .flatMap(student -> student.subjects()
        .map(subject -> new SimpleEntry(subject, student)))
    

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

  3. .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
    

    我们正在将值收集到地图中,将主题设置为关键,将学生设置为结果地图的值。我们传入第三个参数(BinaryOperator)来定义如果发生键冲突应该发生的情况。 我们无法传递null,因此我们使用前哨值 1
    此时,我们通过将每个学科映射到一个学生(或者如果一个学科有多个学生,则为SENTINEL_VALUE)来颠倒学生inverted学科的关系。

  4. .values());
    

    我们获取地图的值,得出具有唯一主题的所有学生的列表以及前哨值。

  5. list.remove(Student.SENTINEL_VALUE);
    

    剩下要做的就是摆脱哨兵值。


1 在这种情况下,我们不能使用null。 Map的大多数实现在映射到null的密钥与缺少该特定密钥之间没有区别。或者,更准确地说,当重映射函数返回HashMap时,null actively removes a node的合并方法。如果要避免使用哨兵值,则必须实现或拥有merge方法,该方法可以像这样return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));来实现。

答案 1 :(得分:2)

另一种解决方案。看起来有点类似于尤金。

Stream.of(stud1, stud2, stud3, stud4)
    .flatMap( s -> s.getSubjects().stream().map( subj -> new AbstractMap.SimpleEntry<>( subj, s ) ) )
    .collect( Collectors.groupingBy(Map.Entry::getKey) )

    .entrySet().stream()
    .filter( e -> e.getValue().size() == 1 )
    .map( e -> e.getValue().get(0).getValue().getName() )
    .collect( Collectors.toSet() );

答案 2 :(得分:1)

您可能可以通过以下更简单的方式进行操作:

Stream<Student> studentStream = Stream.of(stud1, stud2, stud3);

// collect all the unique subjects into a Set
Set<String> uniqueSubjects = studentStream
        .flatMap(st -> st.getSubjects().stream()
                .map(subj -> new AbstractMap.SimpleEntry<>(st.getName(), subj)))
         // subject to occurence count map
        .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.counting()))
        .entrySet()
        .stream()
        .filter(x -> x.getValue() == 1) // occurs only once
        .map(Map.Entry::getKey)  // Q ->  map keys are anyway unique 
        .collect(Collectors.toSet()); // ^^ ... any way to optimise this?(keySet)

// amongst the students, filter those which have any unique subject in their subject list
List<String> studentsStudyingUniqueSubjects = studentStream
        .filter(stud -> stud.getSubjects().stream()
                .anyMatch(uniqueSubjects::contains))
        .map(Student::getName)
        .collect(Collectors.toList());

答案 3 :(得分:1)

不是最易读的解决方案,但是您可以使用:

studentStream.flatMap(st -> st.getSubjects().stream().map(subj -> new SimpleEntry<>(st.getName(), subj)))
                 .collect(Collectors.toMap(
                     Entry::getValue,
                     x -> {
                         List<String> list = new ArrayList<>();
                         list.add(x.getKey());
                         return list;
                     },
                     (left, right) -> {
                         left.addAll(right);
                         return left;
                     }
                 ))
                 .entrySet()
                 .stream()
                 .filter(x -> x.getValue().size() == 1)
                 .map(Entry::getValue)
                 .flatMap(List::stream)
                 .distinct()
                 .forEachOrdered(System.out::println);