Shortcut for adding to List in a HashMap

115,369

Solution 1

In Java 8 you can make use of Map#computeIfAbsent().

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

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

Or, make use of Stream API's Collectors#groupingBy() to go from List to Map directly:

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

In Java 7 or below, best what you can get is below:

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

Commons Collections has a LazyMap, but it's not parameterized. Guava doesn't have sort of a LazyMap or LazyList, but you can use Multimap for this as shown in answer of polygenelubricants below.

Solution 2

Guava's Multimap really is the most appropriate data structure for this, and in fact, there is Multimaps.index(Iterable<V>, Function<? super V,K>) utility method that does exactly what you want: take an Iterable<V> (which a List<V> is), and apply the Function<? super V, K> to get the keys for the Multimap<K,V>.

Here's an example from the documentation:

For example,

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

prints

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

In your case you'd write a Function<User,String> userCountryFunction = ....

Solution 3

By using lambdaj you can obtain that result with just one line of code as it follows:

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

Lambdaj also offers lots of other features to manipulate collections with a very readable domain specific language.

Solution 4

When I have to deal with a collection-valued map, I just about always wind up writing a little putIntoListMap() static utility method in the class. If I find myself needing it in multiple classes, I throw that method into a utility class. Static method calls like that are a bit ugly, but they're much cleaner than typing the code out every time. Unless multi-maps play a pretty central role in your app, IMHO it's probably not worth it to pull in another dependency.

Solution 5

We seem to do this a lot of times so I created a template class

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

You just provide impl for groupBy

in your case

String groupBy(User u){return user.getCountry();}
Share:
115,369
Damo
Author by

Damo

Technologist and increasingly distant from the tools

Updated on July 05, 2022

Comments

  • Damo
    Damo about 2 years

    I often have a need to take a list of objects and group them into a Map based on a value contained in the object. Eg. take a list of Users and group by Country.

    My code for this usually looks like:

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

    However I can't help thinking that this is awkward and some guru has a better approach. The closest I can see so far is the MultiMap from Google Collections.

    Are there any standard approaches?

    Thanks!