Shortcut for adding to List in a HashMap

JavaCollectionsHashmap

Java Problem Overview


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!

Java Solutions


Solution 1 - Java

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 - Java

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 badGuys > = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); > Function stringLengthFunction = ...; > Multimap 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 - Java

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 4 - Java

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 5 - Java

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

Solution 6 - Java

It looks like your exact needs are met by LinkedHashMultimap in the GC library. If you can live with the dependencies, all your code becomes:

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

insertion order is maintained (about all it looks like you were doing with your list) and duplicates are precluded; you can of course switch to a plain hash-based set or a tree set as needs dictate (or a list, though that doesn't seem to be what you need). Empty collections are returned if you ask for a country with no users, everyone gets ponies, etc - what I mean is, check out the API. It'll do a lot for you, so the dependency might be worth it.

Solution 7 - Java

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

Solution 8 - Java

A clean and readable way to add an element is the following:

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

Note that calling containsKey and get is not slower than just calling get and testing the result for null.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDamoView Question on Stackoverflow
Solution 1 - JavaBalusCView Answer on Stackoverflow
Solution 2 - JavapolygenelubricantsView Answer on Stackoverflow
Solution 3 - JavaLuke MaurerView Answer on Stackoverflow
Solution 4 - JavaMario FuscoView Answer on Stackoverflow
Solution 5 - JavaJukeyView Answer on Stackoverflow
Solution 6 - JavaCarlView Answer on Stackoverflow
Solution 7 - Javauser1529313View Answer on Stackoverflow
Solution 8 - JavastarblueView Answer on Stackoverflow