java - simplificador - ¿Cómo agrupa los elementos en una Lista<P> a un Mapa<K, Lista<V>> mientras mantiene el orden?
producto de sumas karnaugh (4)
Tengo una lista de objetos de Google PlaceSummary tomada de la API de Google Places. Me gustaría recopilarlos y agruparlos por su ID de lugar de Google, pero también conservar el orden de los elementos. Lo que pensé que funcionaría sería:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(PlaceSummary::getPlaceId, toList())
));
Pero ni siquiera compilará. Parece que debería de acuerdo con la documentación de la API de Java en Collectors .
Anteriormente tenía este código:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId = places.stream()
.collect(Collectors.groupingBy(PlaceSummary::getPlaceId));
Sin embargo, el estándar .collect()
en la API de Streams no conserva el orden de los elementos en el HashMap
subsiguiente (obviamente, ya que los HashMap
no están ordenados). Deseo que la salida sea un LinkedHashMap
para que el mapa esté ordenado por el orden de inserción de cada grupo.
Sin embargo, la solución que sugerí no compila. En primer lugar, no reconoce el PlaceSummary::getPlaceId
ya que dice que no es una función, aunque sé que lo es. En segundo lugar, dice que no puedo convertir LinkedHashMap<Object, Object>
en M. Se supone que M es una colección genérica, por lo que debería aceptarse.
¿Cómo convierto la Lista en un LinkedHashMap
usando la API de Java Stream? ¿Hay una manera sucinta de hacerlo? Si es demasiado difícil de entender, puedo recurrir a los métodos pre-Java 8 de la vieja escuela.
Noté que hay otra respuesta de desbordamiento de pila al convertir Lista a LinkedHashMap , pero esta no tiene la solución que quiero, ya que necesito recopilar "esto" sobre el objeto sobre el que estoy iterando específicamente.
Creo que te confundiste un poco con el coleccionista final. Simplemente representa lo que necesita estar en cada valor de mapa. No es necesario tener un colector de mapping
secundario, ya que solo desea una lista de los objetos originales.
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.toList()));
Estás realmente cerca de lo que quieres:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(Function.identity(), Collectors.toList())
));
En el método Collectors.mapping
, debe proporcionar la instancia de PlaceSummary
y no el ID de lugar. En el código anterior, utilicé Function.identity()
: este recopilador se usa para construir los valores, por lo que necesitamos acumular los lugares (y no su ID).
Tenga en cuenta que es posible escribir directamente Collectors.toList()
lugar de Collectors.mapping(Function.identity(), Collectors.toList())
.
El código que tiene hasta ahora no se compila porque en realidad está creando un Map<String, List<String>>
: está acumulando los ID para cada ID (lo cual es bastante extraño).
Podría escribir esto como un método genérico:
private static <K, V> Map<K, List<V>> groupByOrdered(List<V> list, Function<V, K> keyFunction) {
return list.stream()
.collect(Collectors.groupingBy(
keyFunction,
LinkedHashMap::new,
Collectors.toList()
));
}
y úsalo así:
Map<String, List<PlaceSummary>> placesGroupedById = groupByOrdered(places, PlaceSummary::getPlaceId);
Si necesita una agrupación mientras mantiene el orden y aplica una función (Reducción), quizás contando, yo uso algo como esto.
final Map<Integer,Long>map=stream.collect(Collectors.groupingBy(function
,LinkedHashMap::new
,Collectors.collectingAndThen(Collectors.counting(),Function.identity()))
)
/**
* I have written this code more generic, if you want then you can group based on any *
* instance variable , id, name etc via passing method reference.
**/
class Student {
private int id;
private String name;
public Student(int id, String name) {this.id = id;this.name = name;}
/**
* @return the id
*/
public int getId() {return id;}
/**
* @param id
* the id to set
*/
public void setId(int id) {this.id = id;}
/**
* @return the name
*/
public String getName() {return name;}
/**
* @param name
* the name to set
*/
public void setName(String name) {this.name = name;}
}
public class StudentMain {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student(1, "Amit"));
list.add(new Student(2, "Sumit"));
list.add(new Student(1, "Ram"));
list.add(new Student(2, "Shyam"));
list.add(new Student(3, "Amit"));
list.add(new Student(4, "Pankaj"));
Map<?, List<Student>> studentById = groupByStudentId(list,
Student::getId);
System.out.println(studentById);
Map<?, List<Student>> studentByName = groupByStudentId(list,
Student::getName);
System.out.println(studentByName);
}
private static <K, V> Map<?, List<V>> groupByStudentId(List<V> list,
Function<V, K> keyFunction) {
return list.stream().collect(
Collectors.groupingBy(keyFunction, HashMap::new,
Collectors.toList()));
}
}