Группировка элементов списка в подсписки (возможно, с использованием guava) - PullRequest
36 голосов
/ 11 декабря 2011

Я хочу сгруппировать элементы списка.В настоящее время я делаю это следующим образом:

public static <E> List<List<E>> group(final List<E> list, final GroupFunction<E> groupFunction) {

    List<List<E>> result = Lists.newArrayList();

    for (final E element : list) {

        boolean groupFound = false;
        for (final List<E> group : result) {
            if (groupFunction.sameGroup(element, group.get(0))) {
                group.add(element);
                groupFound = true;
                break;
            }
        }
        if (! groupFound) {

            List<E> newGroup = Lists.newArrayList();
            newGroup.add(element);
            result.add(newGroup);
        }
    }

    return result;
}

public interface GroupFunction<E> {
    public boolean sameGroup(final E element1, final E element2);
}

Есть ли лучший способ сделать это, желательно с помощью гуавы?

Ответы [ 4 ]

70 голосов
/ 11 декабря 2011

Конечно, это возможно, и даже проще с Гуавой :) Используйте Multimaps.index(Iterable, Function):

ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction);

Если вы приведете конкретный вариант использования, было бы легче показать его в действии.

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

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], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}

В вашем случае, если GroupFunction определена как:

GroupFunction<String> groupFunction = new GroupFunction<String>() {
  @Override public String sameGroup(final String s1, final String s2) {
    return s1.length().equals(s2.length());
  }
}

тогда это будет означать:

Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {
  @Override public Integer apply(final String s) {
    return s.length();
  }
}

возможная реализация stringLengthFunction, использованная в примере с Guava.


Наконец, в Java 8 весь фрагмент кода может быть еще проще, поскольку ссылки на lambas и методы достаточно кратки, чтобы их можно было вставить в строку:

ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length);

Для примера с чистой Java 8 (без гуавы), используя Collector.groupingBy см. Ответ Джеффри Босбума , хотя в этом подходе есть несколько отличий:

  • не возвращает ImmutableListMultimap, а Map со значениями Collection,
  • Нет никаких гарантий относительно типа, изменчивости, сериализуемости или поточной безопасности возвращаемой карты ( source ),

  • это немного более многословно, чем ссылка на метод Guava +.

РЕДАКТИРОВАТЬ : Если вам не нужны индексированные ключи, вы можете получить сгруппированные значения:

List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), new Function<E, List<E>>() {
        @Override public List<E> apply(E key) {
            return indexed.get(key);
        }
});

// or the same view, but with Java 8 lambdas:
List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), indexed::get);

что дает вам Lists<List<E>> вид, содержимое которого можно легко скопировать в ArrayList или просто использовать как есть, как вы и хотели. Также обратите внимание, что indexed.get(key) это ImmutableList.

// bonus: similar as above, but not a view, instead collecting to list using streams:
List<List<E>> grouped = indexed.keySet().stream()
    .map(indexed::get)
    .collect(Collectors.toList());

РЕДАКТИРОВАТЬ 2 : Как Петр Гладких упоминает в комментарии ниже , если Collection<List<E>> достаточно, приведенный выше пример может быть проще:

Collection<List<E>> grouped = indexed.asMap().values();
10 голосов
/ 21 сентября 2014

Collector.groupingBy из библиотеки потоков Java 8 обеспечивает ту же функциональность, что и Multimaps.index в Guava. Вот пример из ответа Xaerxess , переписанного для использования потоков Java 8:

List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Map<Integer, List<String>> index = badGuys.stream()
    .collect(Collectors.groupingBy(String::length));
System.out.println(index);

Это напечатает

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

Если вы хотите объединить значения с одним и тем же ключом иным способом, чем создание списка, вы можете использовать перегрузку groupingBy, которая использует другой коллектор. Этот пример объединяет строки с разделителем:

Map<Integer, String> index = badGuys.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.joining(" and ")));

Это напечатает

{4=Inky, 5=Pinky and Pinky and Clyde, 6=Blinky}

Если у вас большой список или ваша функция группировки стоит дорого, вы можете идти параллельно, используя parallelStream и параллельный коллектор.

Map<Integer, List<String>> index = badGuys.parallelStream()
    .collect(Collectors.groupingByConcurrent(String::length));

Может печататься (порядок больше не является детерминированным)

{4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
4 голосов
/ 10 июля 2014

Самым простым и простым способом было бы использовать: Функция группировки Lamdaj

Приведенный выше пример можно переписать:

List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Group group = group(badGuys, by(on(String.class).length)));
System.out.println(group.keySet());
1 голос
/ 28 января 2015

С помощью Java 8, Guava и нескольких вспомогательных функций вы можете реализовать группировку с помощью специального компаратора

public static <T> Map<T, List<T>> group(List<T> items, Comparator<T> comparator)
{
    ListMultimap<T, T> blocks = LinkedListMultimap.create();

    if (!ArrayUtils.isNullOrEmpty(items))
    {
        T currentItem = null;

        for (T item : items)
        {
            if (currentItem == null || comparator.compare(currentItem, item) != 0)
            {
                currentItem = item;
            }

            blocks.put(currentItem, ObjectUtils.clone(item));
        }
    }

    return Multimaps.asMap(blocks);
}

Пример

Comparator<SportExercise> comparator = Comparator.comparingInt(SportExercise::getEstimatedTime)
                .thenComparingInt(SportExercise::getActiveTime).thenComparingInt(SportExercise::getIntervalCount)
                .thenComparingLong(SportExercise::getExerciseId);

Map<SportExercise, List<SportExercise>> blocks = group(sportWorkout.getTrainingExercises(), comparator);

blocks.forEach((key, values) -> {
            System.out.println(key);
            System.out.println(values);
        });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...