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

je veux 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 la goyave?

33
demandé sur ekad 2011-12-11 15:21:52

4 réponses

bien sûr, il est possible, et encore plus facile avec la goyave :) utiliser Multimaps.index(Iterable, Function) :

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

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

exemple tiré de docs:

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

imprime

{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 il traduirait à:

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

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


enfin, en Java 8, tout l'extrait pourrait être encore plus simple, comme lambas et les références de méthode sont assez concises pour être inligné:

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

Par pure Java 8 (pas de Goyave) exemple d'utilisation de Collector.groupingBy voir Jeffrey Bosboom la réponse de , bien qu'il n'y peu de différences dans cette approche:

  • il n'a pas de retour ImmutableListMultimap mais plutôt Map avec Collection valeurs",
  • Il n'y a pas de garanties sur le type, la mutabilité, serializability, ou le fil de sécurité de la Carte retournée ( source ),

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

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 que vous offre Lists<List<E>> afficher le contenu peut être facilement copié ArrayList ou tout simplement utilisé comme il est, comme vous le souhaitiez, 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 : As Petr Gladkikh 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();
66
répondu Xaerxess 2018-01-05 15:42:16

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

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

ce sera imprimé

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

Si vous souhaitez combiner les valeurs avec la même clé d'une autre façon que de créer une liste, vous pouvez utiliser la surcharge de groupingBy qui prend un autre collectionneur. 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 ")));

ce sera imprimé

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

si vous avez une grande liste ou si votre fonction de regroupement est chère, vous pouvez aller en parallèle en utilisant parallelStream et un collecteur concurrent.

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

Cela peut imprimer (l'ordre n'est plus déterministe)

{4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
9
répondu Jeffrey Bosboom 2014-12-29 06:34:04

la façon la plus simple et la plus simple serait d'utiliser: caractéristique de groupement 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
répondu David Zhao 2014-07-09 23:40:24

avec Java 8, Guava et peu de fonctions d'aide, vous pouvez mettre en œuvre le groupement 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
répondu kayz1 2015-01-28 11:58:49