java - multiple - Cadena de colectores grupales de forma manual.
lambda group by java (2)
Quiero agrupar una lista de personas. Una persona tiene algunos atributos como nombre, país, ciudad, código postal, etc. Escribí el código estático, que funciona muy bien:
Object groupedData = data.stream().collect(groupingBy(Person::getName, Collectors.groupingBy(Person::getCountry, Collectors.groupingBy(Person::getTown))));
Pero el problema es que no es dinámico. A veces solo quiero agrupar por nombre y ciudad, a veces por atributos. ¿Cómo puedo hacer esto? Las soluciones que no sean de Java 8 también son bienvenidas.
Podría crear una función que tome un número arbitrario de atributos para agrupar y construir la Collector
- Collector
en un bucle, cada vez que pase la versión anterior de sí misma como el recopilador downstream
.
public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) {
Iterator<Function<T, ?>> iter = Arrays.asList(groupers).iterator();
Collector collector = Collectors.groupingBy(iter.next());
while (iter.hasNext()) {
collector = Collectors.groupingBy(iter.next(), collector);
}
return (Map) data.stream().collect(collector);
}
Tenga en cuenta que el orden de las funciones del grouper
se invierte, por lo que debe pasarlas en orden inverso (o invertirlas dentro de la función, por ejemplo, utilizando Collections.reverse
o Guava''s Lists.reverse
, según prefiera).
Object groupedData = collectMany(data, Person::getTown, Person::getCountry, Person::getName);
O así, usar un bucle for
vieja escuela for
revertir la matriz en la función, es decir, no tiene que pasar a los meros en orden inverso (pero en mi humilde opinión es más difícil de comprender):
public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) {
Collector collector = Collectors.groupingBy(groupers[groupers.length-1]);
for (int i = groupers.length - 2; i >= 0; i--) {
collector = Collectors.groupingBy(groupers[i], collector);
}
return (Map) data.stream().collect(collector);
}
Ambos enfoques devolverán un Map<?,Map<?,Map<?,T>>>
, al igual que en su código original. Dependiendo de lo que desee hacer con esos datos, también podría valer la pena considerar el uso de un Map<List<?>,T>
, como lo sugiere Tunaki .
Puede agrupar por una lista formada por los atributos que desea agrupar por.
Imagina que quieres agrupar por nombre y país. Entonces podrías usar:
Map<List<Object>, List<Person>> groupedData =
data.stream().collect(groupingBy(p -> Arrays.asList(p.getName(), p.getCountry())));
Esto funciona porque dos listas se consideran iguales cuando contienen el mismo elemento en el mismo orden. Por lo tanto, en el mapa resultante, tendrá una clave para cada par de nombre / país diferente y como valor la lista de personas con ese nombre y país específicos. Dicho de otra manera, en lugar de decir "grupo por nombre, luego grupo por país", efectivamente dice "grupo por nombre y país". La ventaja es que no terminas con un mapa de mapas de mapas, etc.