web-dev-qa-db-fra.com

Regrouper les éléments d'une liste en sous-listes (peut-être en utilisant de la goyave)

Je souhaite regrouper des éléments d'une liste. Je le fais actuellement de cette façon:

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

Y a-t-il une meilleure façon de le faire, de préférence en utilisant de la goyave?

36
Fabian Zeindl

Bien sûr, c'est possible, et encore plus facile avec Guava :) Utilisez Multimaps.index(Iterable, Function) :

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

Si vous donnez un cas d'utilisation concret, il serait plus facile de le montrer en action.

Exemple de documents:

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

Dans votre cas, si GroupFunction est défini comme:

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

alors cela se traduirait par:

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

qui est l'implémentation stringLengthFunction possible utilisée dans l'exemple de Guava.


Enfin, dans Java 8, l'extrait entier pourrait être encore plus simple, car les références lambas et de méthode sont suffisamment concises pour être intégrées:

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

Pour un exemple Java 8 (pas de goyave) utilisant Collector.groupingBy voir réponse de Jeffrey Bosboom , bien qu'il y ait peu de différences dans cette approche:

  • il ne renvoie pas ImmutableListMultimap mais plutôt Map avec des valeurs de Collection,
  • Il n'y a aucune garantie sur le type, la mutabilité, la sérialisation ou la sécurité des threads de la carte retournée ( source ),

  • c'est un peu plus verbeux que la référence à la méthode Guava +.

[~ # ~] modifier [~ # ~] : Si vous ne vous souciez pas des clés indexées, vous pouvez récupérer des valeurs groupées:

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

ce qui vous donne Lists<List<E>> voir quel contenu peut être facilement copié dans ArrayList ou simplement utilisé tel quel, comme vous le vouliez en premier lieu. Notez également que indexed.get(key) est 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());

EDIT 2 : Comme Petr Gladkikh le mentionne dans le commentaire ci-dessous , si Collection<List<E>> Est suffisant, l'exemple ci-dessus pourrait être plus simple:

Collection<List<E>> grouped = indexed.asMap().values();
71
Xaerxess

Collector.groupingBy de la bibliothèque Java 8 streams fournit les mêmes fonctionnalités que Multimaps.index De Guava. Voici l'exemple de Xaerxess's réponse , réécrit pour utiliser Java 8 flux:

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

Cela imprimera

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

Si vous souhaitez combiner les valeurs avec la même clé d'une autre manière que la création d'une liste, vous pouvez utiliser la surcharge de groupingBy qui prend un autre collecteur. Cet exemple concatène les chaînes avec un délimiteur:

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

Cela imprimera

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

Si vous avez une grande liste ou que votre fonction de regroupement est coûteuse, vous pouvez aller en parallèle en utilisant parallelStream et un collecteur simultané.

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

Cela peut s'imprimer (la commande n'est plus déterministe)

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

La manière la plus simple et la plus simple serait d'utiliser: fonction de regroupement Lamdaj

L'exemple ci-dessus peut être réécrit:

List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Group group = group(badGuys, by(on(String.class).length)));
System.out.println(group.keySet());
4
David Zhao

Avec Java 8, Guava et quelques fonctions d'assistance, vous pouvez implémenter le regroupement avec un comparateur personnalisé

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

Exemple

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