A menudo tengo la necesidad de tomar una lista de objetos y agruparlos en un Mapa basado en un valor contenido en el objeto. P.ej. tome una lista de Usuarios y agrupe por País.
Mi código para esto generalmente se ve así:
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
if(usersByCountry.containsKey(user.getCountry())) {
//Add to existing list
usersByCountry.get(user.getCountry()).add(user);
} else {
//Create new list
List<User> users = new ArrayList<User>(1);
users.add(user);
usersByCountry.put(user.getCountry(), users);
}
}
Sin embargo, no puedo evitar pensar que esto es incómodo y que algún gurú tiene un mejor enfoque. Lo más cercano que puedo ver hasta ahora es el MultiMap de Google Collections.
¿Existen enfoques estándar?
¡Gracias!
BalusC
Desde Java 8 puedes hacer uso de Map#computeIfAbsent()
.
Map<String, List<User>> usersByCountry = new HashMap<>();
for (User user : listOfUsers) {
usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}
O bien, utilice las API de transmisión Collectors#groupingBy()
ir de List
a Map
directamente:
Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));
En Java 7 o inferior, lo mejor que puede obtener es lo siguiente:
Map<String, List<User>> usersByCountry = new HashMap<>();
for (User user : listOfUsers) {
List<User> users = usersByCountry.get(user.getCountry());
if (users == null) {
users = new ArrayList<>();
usersByCountry.put(user.getCountry(), users);
}
users.add(user);
}
Colecciones comunes tiene un LazyMap
pero no está parametrizado. Guayaba no tiene una especie de LazyMap
o LazyList
pero puedes usar Multimap
para esto, como se muestra en la respuesta de polygenelubricants a continuación.
-
Podrías acortarlo un poco más:
usersByCountry.put(user.getCountry(), users = new ArrayList<>());
Aunque estoy seguro de que algunos fruncirían el ceño.– shmosel
17 de marzo de 2017 a las 1:30
-
Genial, una lista mutable como valor.
– WesternGun
hace 2 días
poligenelubricantes
guayaba Multimap
realmente es la estructura de datos más apropiada para esto, y de hecho, hay Multimaps.index(Iterable<V>, Function<? super V,K>)
método de utilidad que hace exactamente lo que quiere: tomar un Iterable<V>
(que un List<V>
es), y aplicar el Function<? super V, K>
para conseguir las llaves de la Multimap<K,V>
.
Aquí hay un ejemplo de la documentación:
Por ejemplo,
List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); Function<String, Integer> stringLengthFunction = ...; Multimap<Integer, String> index = Multimaps.index(badGuys, stringLengthFunction); System.out.println(index);
huellas dactilares
{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
En tu caso, escribirías un Function<User,String> userCountryFunction = ...
.
-
+1 Me frustra que las respuestas que involucran escribir mucho más código que este tengan una clasificación más alta, solo porque fueron las más rápidas en llegar. 🙁
–Kevin Bourrillion
11 de junio de 2010 a las 16:37
-
@Kevin: Esperaba que eventualmente pasaras por aquí =) Por cierto, planeo eventualmente escribir artículos de preguntas y respuestas sobre stackoverflow en varias clases de guayaba para demostrar sus capacidades.
– lubricantes poligenéticos
11 de junio de 2010 a las 16:39
-
Paso solo una o dos veces al día, lo que garantiza que nunca tendré la oportunidad de que mis respuestas sean votadas.
Creo que tu idea es genial. Supongo que te refieres a publicar una pregunta y responderla tú mismo. Obtendrá algunas personas que le dirán que hay algo inmoral en esto, pero está explícitamente sancionado por la comunidad SO en general, ya que su objetivo es que SO tenga un gran contenido. –Kevin Bourrillion
11 de junio de 2010 a las 16:47
-
+1 para el ejemplo. Yo sería curioso a los artículos. Se debe prestar más atención a las bibliotecas de guayaba.
– BalusC
11 de junio de 2010 a las 16:49
Cuando tengo que lidiar con un mapa con valor de colección, casi siempre termino escribiendo un pequeño método de utilidad estática putIntoListMap() en la clase. Si me encuentro necesitándolo en varias clases, lanzo ese método a una clase de utilidad. Las llamadas a métodos estáticos como ese son un poco feas, pero son mucho más limpias que escribir el código cada vez. A menos que los mapas múltiples jueguen un papel bastante central en su aplicación, en mi humilde opinión, probablemente no valga la pena obtener otra dependencia.
Mediante el uso lambdaj puede obtener ese resultado con solo una línea de código de la siguiente manera:
Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));
Lambdaj también ofrece muchas otras características para manipular colecciones con un lenguaje específico de dominio muy legible.
Parece que hacemos esto muchas veces, así que creé una clase de plantilla
public abstract class ListGroupBy<K, T> {
public Map<K, List<T>> map(List<T> list) {
Map<K, List<T> > map = new HashMap<K, List<T> >();
for (T t : list) {
K key = groupBy
List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>();
innerList.add
map.put(key, innerList);
}
return map;
}
protected abstract K groupBy(T t);
}
Solo proporciona impl para groupBy
en tu caso
String groupBy(User u){return user.getCountry();}
Carlos
Parece que sus necesidades exactas son satisfechas por LinkedHashMultimapa en la biblioteca de GC. Si puede vivir con las dependencias, todo su código se convierte en:
SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);
se mantiene el orden de inserción (sobre todo lo que parece que estaba haciendo con su lista) y se excluyen los duplicados; por supuesto, puede cambiar a un conjunto simple basado en hash o un conjunto de árboles según lo dicten las necesidades (o una lista, aunque eso no parece ser lo que necesita). Las colecciones vacías se devuelven si solicita un país sin usuarios, todos obtienen ponis, etc. Lo que quiero decir es que consulte la API. Hará mucho por ti, por lo que la dependencia podría valer la pena.
usuario1529313
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
List<User> users = usersByCountry.get(user.getCountry());
if (users == null) {
usersByCountry.put(user.getCountry(), users = new ArrayList<User>());
}
users.add(user);
}
¿Debería ser eso realmente?
Map<String, Set<User>>
? La respuesta hace una diferencia para lo que elijas construir o usar. Tenga en cuenta que Google Collections proporciona mejoras para las colecciones anidadas que son de varios tipos de listas y conjuntos.– seh
11 de junio de 2010 a las 0:04
Simplemente suelte Java para .Net y Linq.
– Hamish Grubijan
11 de junio de 2010 a las 0:11
@Hamish: sí, ¡debido a que nuestras preocupaciones sobre las dependencias son totalmente irrelevantes entonces!
– Carlos
11 de junio de 2010 a las 1:20
@Hamish: Nunca podría volver a enfrentar a mi hermano programador .Net si hiciera eso. ¡La verguenza!
– Damo
11 de junio de 2010 a las 6:14