Ярлык для добавления в список в HashMap - PullRequest
47 голосов
/ 11 июня 2010

Мне часто нужно взять список объектов и сгруппировать их в карту на основе значения, содержащегося в объекте.Например.возьмите список пользователей и групп по странам.

Мой код для этого обычно выглядит так:

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

Однако я не могу не думать, что это неудобно, и у некоторых гуруподход.На данный момент наиболее близким является MultiMap из Google Collections .

Существуют ли стандартные подходы?

Спасибо!

Ответы [ 8 ]

64 голосов
/ 11 июня 2010

В Java 8 вы можете использовать Map#computeIfAbsent().

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

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

Или использовать Stream API Collectors#groupingBy() для перехода от List до Map напрямую:

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

В Java 7 или ниже лучшее, что вы можете получить, это ниже:

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 имеетLazyMap, но он не параметризован. Гуава не имеет вида LazyMap или LazyList, но вы можете использовать для этого Multimap, как показано в ответе полигенасмазочных материалов ниже .

20 голосов
/ 11 июня 2010

Guava's Multimap действительно является наиболее подходящей структурой данных для этого, и на самом деле, существует Multimaps.index(Iterable<V>, Function<? super V,K>) служебный метод, который делает именно то, что вы хотите: 1007 *Iterable<V> (что является List<V>) и примените Function<? super V, K>, чтобы получить ключи для Multimap<K,V>.

Вот пример из документации:

Например,

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

печать

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

В вашем случае вы бы написали Function<User,String> userCountryFunction = ....

2 голосов
/ 04 октября 2012

Мы, кажется, делаем это много раз, поэтому я создал шаблон класса

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

Вы просто предоставляете impl для groupBy

в вашем случае

String groupBy(User u){return user.getCountry();}
2 голосов
/ 14 июня 2010

Используя lambdaj , вы можете получить этот результат всего одной строкой кода следующим образом:

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

Lambdaj также предлагает множество других функций для управления коллекциями с очень удобочитаемымспецифичный для домена язык.

2 голосов
/ 11 июня 2010

Когда мне приходится иметь дело с коллекционной картой, я почти всегда пишу небольшой метод статической утилиты putIntoListMap () в классе. Если мне понадобится несколько классов, я добавлю этот метод в служебный класс. Подобные вызовы статических методов немного уродливы, но они намного чище, чем каждый раз вводить код. Если мультикарты не играют довольно важную роль в вашем приложении, IMHO, вероятно, не стоит ввязываться в другую зависимость.

1 голос
/ 11 июня 2010

Похоже, что ваши точные потребности удовлетворяются LinkedHashMultimap в библиотеке GC. Если вы можете жить с зависимостями, весь ваш код становится:

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

поддерживается порядок вставки (все выглядит так, как будто вы делали со своим списком), а дубликаты исключаются; Вы можете, конечно, переключиться на простой набор на основе хеша или набор деревьев по необходимости (или список, хотя это, кажется, не то, что вам нужно). Пустые коллекции возвращаются, если вы запрашиваете страну без пользователей, все получают пони и т. Д. Я имею в виду, ознакомьтесь с API. Это многое сделает для вас, поэтому зависимость может стоить того.

0 голосов
/ 16 июля 2014
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 голосов
/ 11 июня 2010

Простой и понятный способ добавить элемент:

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

Обратите внимание, что вызов containsKey и get не медленнее, чем просто вызов get и проверка результата на null.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...