Recientemente tuve una entrevista técnica y tengo tarea de codificación de pequeña API Stream.
Consideremos siguiente entrada:
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);
La tarea es encontrar estudiantes con temas únicos que utilizan la API de corriente .
Así que por el resultado esperado provisto de entrada (orden de ignorar) es [John, Anthony]
.
Presenté la solución utilizando colector personalizado:
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);
Pero la solución se considera como no óptima / eficiente.
Podría, por favor comparta sus ideas sobre la solución más eficiente para esta tarea?
ACTUALIZACIÓN: Tengo otra opinión de un chico que iba a usar (método Stream.reduce ()) reductor. Pero no puedo entender cómo esto podría aumentar la eficiencia. ¿Qué piensas?
Aquí es otra.
// 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);
(Intencionalmente utilizando un valor centinela, más sobre esto más adelante).
Los pasos:
Set<Student> list = new HashSet<>(studentStream
Estamos creando un HashSet de la colección que vamos a recoger. Esto se debe a que queremos para deshacerse de los estudiantes duplicados (estudiantes con múltiples sujetos singulares, en su caso Antony).
.flatMap(student -> student.subjects() .map(subject -> new SimpleEntry(subject, student)))
Estamos flatmapping temas de cada estudiante en una corriente, pero primero asignar cada elemento de un par con elementos como la clave del tema y como valor el estudiante. Esto se debe a que necesitamos para retener la asociación entre el sujeto y el estudiante. Estoy usando
AbstractMap.SimpleEntry
, pero, por supuesto, se puede utilizar cualquier aplicación de un par..collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
Estamos recogiendo los valores en un mapa, ponga el asunto como la clave y el estudiante como valor para el mapa resultante. Pasamos en un tercer argumento (a
BinaryOperator
) para definir lo que debería ocurrir si una colisión de llaves se realiza. No podemos pasarnull
, por lo que utilizar un valor centinela 1 .
En este punto, hemos invertido el estudiante relación ↔ sujeto mediante la asignación de cada sujeto a un estudiante (o elSENTINEL_VALUE
si un sujeto tiene varios estudiantes)..values());
Tomamos los valores del mapa, dando la lista de todos los estudiantes con un único tema, más el valor centinela.
list.remove(Student.SENTINEL_VALUE);
Lo único que queda por hacer es deshacerse del valor centinela.
1 No podemos utilizar null
en esta situación. La mayoría de las implementaciones de un mapa no hacen distinción entre una clave asignada a null
o la ausencia de esa clave en particular. O, más exactamente, el método de combinación de HashMap
activamente elimina un nodo cuando la reasignación devuelve la función null
. Si queremos evitar un valor centinela, entonces debemos poner en práctica o el propio merge
método, que podría aplicarse a algo como esto: return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));
.