Более эффективное решение задачи кодирования с использованием Stream API? - PullRequest
0 голосов
/ 12 февраля 2019

Недавно я провел техническое собеседование и получил небольшую задачу по написанию кода в 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);

Но решение было сочтено не оптимальным / эффективным.Не могли бы вы поделиться своими идеями относительно более эффективного решения этой задачи?

ОБНОВЛЕНИЕ: Я получил другое мнение от одного парня, что он будет использовать редуктор (метод Stream.reduce ()).Но я не могу понять, как это может повысить эффективность.Что ты думаешь?

Ответы [ 4 ]

0 голосов
/ 12 февраля 2019

Вот еще один.

// 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, если у предмета несколько учеников).

  4. .values());
    

    Мы берем значения карты, получая список всех студентов с уникальным предметом, плюсзначение часового.

  5. list.remove(Student.SENTINEL_VALUE);
    

    Осталось только избавиться от значения часового.


1 Мы не можем использовать null в этой ситуации.В большинстве реализаций Map нет различия между ключом, сопоставленным с null, или отсутствием этого конкретного ключа.Или, точнее, метод слияния HashMap активно удаляет узел , когда функция переназначения возвращает null.Если мы хотим избежать значения часового, то мы должны реализовать или иметь метод merge, который можно реализовать примерно так: return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));.

0 голосов
/ 12 февраля 2019

Не самое читаемое решение, но вот вам:

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);
0 голосов
/ 12 февраля 2019

Другое решение.Выглядит как-то похоже на Евгения.

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() );
0 голосов
/ 12 февраля 2019

Вы, вероятно, можете сделать это более простым способом:

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());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...