More efficient solution on coding task using Stream API?
Here is another one.
// 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);
(Intentionally using a sentinel value, more about that below.)
The steps:
Set<Student> list = new HashSet<>(studentStream
We're creating a HashSet from the Collection we're going to collect. That's because we want to get rid of the duplicate students (students with multiple unique subjects, in your case Antony).
.flatMap(student -> student.subjects() .map(subject -> new SimpleEntry(subject, student)))
We are flatmapping each student's subjects into a stream, but first we map each element to a pair with as key the subject and as value the student. This is because we need to retain the association between the subject and the student. I'm using
AbstractMap.SimpleEntry
, but of course, you can use any implementation of a pair..collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
We are collecting the values into a map, setting the subject as key and the student as value for the resulting map. We pass in a third argument (a
BinaryOperator
) to define what should happen if a key collision takes place. We cannot pass innull
, so we use a sentinel value1.
At this point, we have inverted the relation student ↔ subject by mapping each subject to a student (or theSENTINEL_VALUE
if a subject has multiple students)..values());
We take the values of the map, yielding the list of all students with a unique subject, plus the sentinel value.
list.remove(Student.SENTINEL_VALUE);
The only thing left to do is getting rid of the sentinel value.
1 We cannot use null
in this situation. Most implementations of a Map make no distinction between a key mapped to null
or the absence of that particular key. Or, more accurately, the merge method of HashMap
actively removes a node when the remapping function returns null
. If we want to avoid a sentinel value, then we must implement or own merge
method, which could be implemented like something like this: return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));
.
Another solution. Looks kind of similar to Eugene.
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() );