web-dev-qa-db-fra.com

Raccourci pour ajouter à la liste dans un HashMap

J'ai souvent besoin de prendre une liste d'objets et de les regrouper dans une carte basée sur une valeur contenue dans l'objet. Par exemple. prendre une liste d'utilisateurs et regrouper par pays.

Mon code pour cela ressemble généralement à:

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);
    }
}

Cependant, je ne peux m'empêcher de penser que c'est gênant et certains gourous ont une meilleure approche. Le plus proche que je peux voir jusqu'à présent est le MultiMap de Google Collections .

Existe-t-il des approches standard?

Merci!

50
Damo

Dans Java 8, vous pouvez utiliser Map#computeIfAbsent() .

Map<String, List<User>> usersByCountry = new HashMap<>();

for (User user : listOfUsers) {
    usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}

Ou utilisez les API Stream Collectors#groupingBy() pour passer de List à Map directement:

Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));

En Java 7 ou inférieur, le mieux que vous pouvez obtenir est ci-dessous:

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);
}

Collections communes a un LazyMap , mais il n'est pas paramétré. Guava n'a pas une sorte de LazyMap ou LazyList, mais vous pouvez utiliser Multimap pour cela comme indiqué en réponse des polygénelubrifiants ci-dessous .

67
BalusC

Multimap de la goyave est vraiment la structure de données la plus appropriée pour cela, et en fait, il existe Multimaps.index(Iterable<V>, Function<? super V,K>) méthode utilitaire qui fait exactement ce que vous voulez: prenez un Iterable<V> (dont un List<V> est), et appliquez le Function<? super V, K> pour obtenir les clés du Multimap<K,V>.

Voici un exemple tiré de la documentation:

Par exemple,

  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);

impressions

 {4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}

Dans votre cas, vous écririez un Function<User,String> userCountryFunction = ....

20
polygenelubricants

Nous semblons le faire souvent, j'ai donc créé une classe de modèles

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(t);
        List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>();
        innerList.add(t);
        map.put(key, innerList);
    }
    return map;
}

protected abstract K groupBy(T t);
}

Vous fournissez juste impl pour groupBy

dans ton cas

String groupBy(User u){return user.getCountry();}
2
Jukey

Quand je dois gérer une carte de valeur de collection, je finis presque toujours par écrire une petite méthode utilitaire statique putIntoListMap () dans la classe. Si je me retrouve en avoir besoin dans plusieurs classes, je jette cette méthode dans une classe utilitaire. Les appels de méthode statique comme celui-ci sont un peu laids, mais ils sont beaucoup plus propres que de taper le code à chaque fois. À moins que les multi-cartes jouent un rôle assez central dans votre application, à mon humble avis, cela ne vaut probablement pas la peine d'attirer une autre dépendance.

2
Luke Maurer

En utilisant lambdaj vous pouvez obtenir ce résultat avec une seule ligne de code comme suit:

Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));

Lambdaj offre également de nombreuses autres fonctionnalités pour manipuler les collections avec un langage spécifique au domaine très lisible.

2
Mario Fusco

Il semble que vos besoins exacts soient satisfaits par LinkedHashMultimap dans la bibliothèque GC. Si vous pouvez vivre avec les dépendances, tout votre code devient:

SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);

l'ordre d'insertion est conservé (à peu près tout ce que vous faites avec votre liste) et les doublons sont exclus; vous pouvez bien sûr basculer vers un ensemble basé sur un hachage simple ou un ensemble d'arbres selon les besoins (ou une liste, bien que cela ne semble pas être ce dont vous avez besoin). Les collections vides sont retournées si vous demandez un pays sans utilisateurs, tout le monde obtient des poneys, etc. - ce que je veux dire, consultez l'API. Cela fera beaucoup pour vous, donc la dépendance en vaut la peine.

1
Carl

Une façon claire et lisible d'ajouter un élément est la suivante:

String country = user.getCountry();
Set<User> users
if (users.containsKey(country))
{
    users = usersByCountry.get(user.getCountry());
}
else
{
    users = new HashSet<User>();
    usersByCountry.put(country, users);
}
users.add(user);

Notez que l'appel de containsKey et get n'est pas plus lent que d'appeler simplement get et de tester le résultat pour null.

0
starblue
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);
}
0
user1529313